diff --git a/Hypercube.Utilities.sln b/Hypercube.Utilities.sln index 0a62cca..0b7609f 100644 --- a/Hypercube.Utilities.sln +++ b/Hypercube.Utilities.sln @@ -8,6 +8,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hypercube.Utilities.Analyze EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hypercube.Utilities.Analyzers.CodeFix", "src\Hypercube.Utilities.Analyzers.CodeFix\Hypercube.Utilities.Analyzers.CodeFix.csproj", "{33E0A347-BAE3-43A1-9D6B-E5AF033CCE16}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Main", "Main\Main.csproj", "{6BB9C8F4-4BA5-45EA-8924-1DE5771DF4BF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -30,5 +32,9 @@ Global {33E0A347-BAE3-43A1-9D6B-E5AF033CCE16}.Debug|Any CPU.Build.0 = Debug|Any CPU {33E0A347-BAE3-43A1-9D6B-E5AF033CCE16}.Release|Any CPU.ActiveCfg = Release|Any CPU {33E0A347-BAE3-43A1-9D6B-E5AF033CCE16}.Release|Any CPU.Build.0 = Release|Any CPU + {6BB9C8F4-4BA5-45EA-8924-1DE5771DF4BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6BB9C8F4-4BA5-45EA-8924-1DE5771DF4BF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6BB9C8F4-4BA5-45EA-8924-1DE5771DF4BF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6BB9C8F4-4BA5-45EA-8924-1DE5771DF4BF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/Main/Main.csproj b/Main/Main.csproj new file mode 100644 index 0000000..fcb2d26 --- /dev/null +++ b/Main/Main.csproj @@ -0,0 +1,14 @@ + + + + Exe + net9.0 + enable + enable + + + + + + + diff --git a/Main/Program.cs b/Main/Program.cs new file mode 100644 index 0000000..e5d72ea --- /dev/null +++ b/Main/Program.cs @@ -0,0 +1,35 @@ +using Hypercube.Utilities.Serialization.Hml; +using Hypercube.Utilities.Serialization.Hml.Core; + +namespace Main; + +public static class Program +{ + public static void Main() + { + var data = new + { + Name = "ТесмиДев", + Age = 20, + Obj = new {}, + Roles = new[] + { + "Programmer", + "Driver" + } + }; + + var options = new HmlSerializerOptions + { + Eol = false, + Indented = true, + IndentSize = 2 + }; + + var serialized = HmlSerializer.Serialize(data, options); + var tokens = HmlLexer.Tokenize(serialized); + + Console.WriteLine(HmlSerializer.Serialize(data, options)); + Console.WriteLine(string.Join(Environment.NewLine, tokens)); + } +} diff --git a/src/Hypercube.Utilities.Analyzers.CodeFix/DependencyCodeFixProvider.cs b/src/Hypercube.Utilities.Analyzers.CodeFix/DependencyCodeFixProvider.cs index c5122dd..d488ff7 100644 --- a/src/Hypercube.Utilities.Analyzers.CodeFix/DependencyCodeFixProvider.cs +++ b/src/Hypercube.Utilities.Analyzers.CodeFix/DependencyCodeFixProvider.cs @@ -5,7 +5,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; -namespace Hypercube.Analyzers.CodeFix; +namespace Hypercube.Utilities.Analyzers.CodeFix; [Shared, ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(DependencyCodeFixProvider))] public sealed class DependencyCodeFixProvider : CodeFixProvider diff --git a/src/Hypercube.Utilities.Analyzers/DependencyAnalyzer.cs b/src/Hypercube.Utilities.Analyzers/DependencyAnalyzer.cs index 65d0ee6..fb0a5dd 100644 --- a/src/Hypercube.Utilities.Analyzers/DependencyAnalyzer.cs +++ b/src/Hypercube.Utilities.Analyzers/DependencyAnalyzer.cs @@ -4,7 +4,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; -namespace Hypercube.Analyzers; +namespace Hypercube.Utilities.Analyzers; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DependencyAnalyzer : DiagnosticAnalyzer diff --git a/src/Hypercube.Utilities.Analyzers/DependencySuppressor.cs b/src/Hypercube.Utilities.Analyzers/DependencySuppressor.cs index f84c20b..31e6b79 100644 --- a/src/Hypercube.Utilities.Analyzers/DependencySuppressor.cs +++ b/src/Hypercube.Utilities.Analyzers/DependencySuppressor.cs @@ -3,7 +3,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; -namespace Hypercube.Analyzers; +namespace Hypercube.Utilities.Analyzers; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DependencySuppressor : DiagnosticSuppressor diff --git a/src/Hypercube.Utilities.UnitTests/Serialization/HmlTests.cs b/src/Hypercube.Utilities.UnitTests/Serialization/HmlTests.cs index 433a1af..7948326 100644 --- a/src/Hypercube.Utilities.UnitTests/Serialization/HmlTests.cs +++ b/src/Hypercube.Utilities.UnitTests/Serialization/HmlTests.cs @@ -117,7 +117,7 @@ public void SerializeTrailingComma() """; - var options = new HmlSerializerOptions { TrailingComma = false }; + var options = new HmlSerializerOptions(); var serialized = HmlSerializer.Serialize(new Weapon(), options); Assert.That(serialized, Is.EqualTo(expected)); diff --git a/src/Hypercube.Utilities/Helpers/ReflectionHelper.cs b/src/Hypercube.Utilities/Helpers/ReflectionHelper.cs index fdd5b3a..fd99cb3 100644 --- a/src/Hypercube.Utilities/Helpers/ReflectionHelper.cs +++ b/src/Hypercube.Utilities/Helpers/ReflectionHelper.cs @@ -1,7 +1,5 @@ using System.Reflection; -using System.Xml.XPath; using Hypercube.Utilities.Extensions; -using Hypercube.Utilities.Serialization.Hml.Core.Nodes.Value; using JetBrains.Annotations; namespace Hypercube.Utilities.Helpers; @@ -249,6 +247,11 @@ public static IReadOnlyList GetValueInfos(BindingFlags? flags = nu return GetValueInfos(typeof(T), flags); } + public static IReadOnlyList GetValueInfos(object obj, BindingFlags? flags = null) + { + return GetValueInfos(obj.GetType(), flags); + } + public static IReadOnlyList GetValueInfos(Type type, BindingFlags? flags = null) { flags ??= DefaultFlags; @@ -262,7 +265,7 @@ public static IReadOnlyList GetValueInfos(Type type, BindingFlags? fl foreach (var info in type.GetFields((BindingFlags) flags)) { - if (info.Name.Contains("k__BackingField")) + if (info.Name.Contains("k__BackingField") || info.Name.Contains("i__Field")) continue; if (Attribute.IsDefined(info, typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute))) diff --git a/src/Hypercube.Utilities/Serialization/Hml/Core/CompilerTypes/BuildASTStackFrame.cs b/src/Hypercube.Utilities/Serialization/Hml/Core/CompilerTypes/BuildASTStackFrame.cs new file mode 100644 index 0000000..e2420a3 --- /dev/null +++ b/src/Hypercube.Utilities/Serialization/Hml/Core/CompilerTypes/BuildASTStackFrame.cs @@ -0,0 +1,17 @@ +using System.Diagnostics; +using Hypercube.Utilities.Serialization.Hml.Core.Nodes; + +namespace Hypercube.Utilities.Serialization.Hml.Core.CompilerTypes; + +[DebuggerDisplay("{Node} ({Parent})")] +public record BuildAstStackFrame +{ + public readonly INode Node; + public readonly INode Parent; + + public BuildAstStackFrame(INode node, INode parent) + { + Node = node; + Parent = parent; + } +} diff --git a/src/Hypercube.Utilities/Serialization/Hml/Core/CompilerTypes/RenderASTStackFrame.cs b/src/Hypercube.Utilities/Serialization/Hml/Core/CompilerTypes/RenderASTStackFrame.cs new file mode 100644 index 0000000..d138abb --- /dev/null +++ b/src/Hypercube.Utilities/Serialization/Hml/Core/CompilerTypes/RenderASTStackFrame.cs @@ -0,0 +1,16 @@ +using Hypercube.Utilities.Serialization.Hml.Core.Nodes; + +namespace Hypercube.Utilities.Serialization.Hml.Core.CompilerTypes; + +public record RenderAstStackFrame +{ + public readonly INode Node; + + public int State; + public int Index; + + public RenderAstStackFrame(INode node) + { + Node = node; + } +} diff --git a/src/Hypercube.Utilities/Serialization/Hml/Core/CompilerTypes/RenderASTState.cs b/src/Hypercube.Utilities/Serialization/Hml/Core/CompilerTypes/RenderASTState.cs new file mode 100644 index 0000000..b333cdf --- /dev/null +++ b/src/Hypercube.Utilities/Serialization/Hml/Core/CompilerTypes/RenderASTState.cs @@ -0,0 +1,23 @@ +namespace Hypercube.Utilities.Serialization.Hml.Core.CompilerTypes; + +public class RenderAstState +{ + public readonly int IndentSize; + + public string Indent { get; private set; } = string.Empty; + + public RenderAstState(int indentSize) + { + IndentSize = indentSize; + } + + public void PushIndent() + { + Indent += new string(' ', IndentSize); + } + + public void PopIndent() + { + Indent = Indent.Remove(Indent.Length - IndentSize); + } +} diff --git a/src/Hypercube.Utilities/Serialization/Hml/Core/HmlCompiler.cs b/src/Hypercube.Utilities/Serialization/Hml/Core/HmlCompiler.cs index d61c190..a486070 100644 --- a/src/Hypercube.Utilities/Serialization/Hml/Core/HmlCompiler.cs +++ b/src/Hypercube.Utilities/Serialization/Hml/Core/HmlCompiler.cs @@ -1,146 +1,152 @@ using System.Collections; using System.Text; using Hypercube.Utilities.Helpers; +using Hypercube.Utilities.Serialization.Hml.Core.CompilerTypes; +using Hypercube.Utilities.Serialization.Hml.Core.Nodes; +using Hypercube.Utilities.Serialization.Hml.Core.Nodes.Value; +using Hypercube.Utilities.Serialization.Hml.Core.Nodes.Value.Primitives; namespace Hypercube.Utilities.Serialization.Hml.Core; public static class HmlCompiler { - private record StackItem(object? Obj, int Indent, string? Name, bool IsClosing = false, bool Enumerated = false); - - public static StringBuilder Serialize(object? obj, HmlSerializerOptions options) + public static string Serialize(object? obj, HmlSerializerOptions options) { - var builder = new StringBuilder(); - var stack = new Stack(); - - stack.Push(new StackItem(obj, 0, null)); + return SerializeToBuilder(obj, options).ToString(); + } + + public static StringBuilder SerializeToBuilder(object? obj, HmlSerializerOptions options) + { + var stack = new Stack(); + var nodeQueue = new Queue(); + + stack.Push(obj); while (stack.Count > 0) { var current = stack.Pop(); - var indent = new string(' ', current.Indent * options.IndentSize); - - if (current.IsClosing) + if (current is Node currentNode) { - builder.Append(indent); - - if (current.Obj is not null) - builder.Append(current.Obj); - - if (current.Enumerated) - builder.Append(','); - - builder.AppendLine(); + nodeQueue.Enqueue(currentNode); continue; } - if (current.Obj is null) + // Null + if (current is null) { - builder.Append(indent); - - if (current.Name is not null) - { - builder.Append(current.Name); - builder.Append(": "); - } - - builder.AppendLine(current.Enumerated ? "null," : "null"); + nodeQueue.Enqueue(new NullValueNode()); continue; } - var type = current.Obj.GetType(); - if (IsSimpleType(current.Obj) || IsStringType(current.Obj)) + // Booleans, numbers, strings + if (IsPrimitiveType(current)) { - builder.Append(indent); - - if (current.Name is not null) - { - builder.Append(current.Name); - builder.Append(": "); - } - - builder.Append(FormatValue(current.Obj, in options)); - - if (current.Enumerated) - builder.Append(','); - - builder.AppendLine(); + nodeQueue.Enqueue(ToPrimitiveValueNode(current)); continue; } - - if (current.Obj is IEnumerable enumerable) - { - builder.Append(indent); - - if (current.Name is not null) - { - builder.Append(current.Name); - builder.Append(": "); - } - builder.Append('['); - builder.AppendLine(); - - var items = new List(); - foreach (var e in enumerable) - items.Add(e!); + // Arrays, lists + if (current is IList list) + { + nodeQueue.Enqueue(new ListNode()); + stack.Push(new EndNode()); - stack.Push(new StackItem("]", current.Indent, null, true, Enumerated: current.Enumerated)); - for (var i = items.Count - 1; i >= 0; i--) - stack.Push(new StackItem(items[i], current.Indent + 1, null, Enumerated: i != items.Count - 1 || options.TrailingComma)); + for (var i = list.Count - 1; i >= 0; i--) + stack.Push(list[i]); continue; } - - builder.Append(indent); - - if (current.Name is not null) - { - builder.Append(current.Name); - builder.Append(": "); - } - - builder.Append('{'); - builder.AppendLine(); - var values = ReflectionHelper.GetValueInfos(type); + // Object + var values = ReflectionHelper.GetValueInfos(current); - stack.Push(new StackItem("}", current.Indent, null, true, Enumerated: current.Enumerated)); + nodeQueue.Enqueue(new ObjectNode()); + stack.Push(new EndNode()); for (var i = values.Count - 1; i >= 0; i--) { var valueInfo = values[i]; - var value = valueInfo.GetValue(current.Obj); - stack.Push(new StackItem(value!, current.Indent + 1, valueInfo.Name)); + var value = valueInfo.GetValue(current); + + stack.Push(value); + stack.Push(new IdentifierNode(valueInfo.Name)); + stack.Push(new KeyValuePairNode()); } } - - return builder; + + return RenderAst(BuildAst(nodeQueue), options); } - private static bool IsSimpleType(object obj) + private static RootNode BuildAst(Queue nodes) { - var type = obj.GetType(); - return type.IsPrimitive || type.IsEnum || type == typeof(decimal) || type == typeof(bool); + var stack = new Stack(); + var ast = new RootNode(); + + stack.Push(new BuildAstStackFrame(ast, null!)); + + while (stack.Count > 0) + { + var frame = stack.Pop(); + + frame.Node.SetParent(frame.Parent); + frame.Node.OnBuild(stack, nodes, frame); + } + + return ast; } - - private static bool IsStringType(object obj) + + private static StringBuilder RenderAst(RootNode ast, HmlSerializerOptions options) { - var type = obj.GetType(); - return type == typeof(string) || type == typeof(char); + var result = new StringBuilder(); + + var state = new RenderAstState(options.IndentSize); + var stack = new Stack(); + + stack.Push(new RenderAstStackFrame(ast)); + + while (stack.Count > 0) + { + var frame = stack.Pop(); + result.Append(frame.Node.Render(stack, result, frame, state, options)); + } + + return result; } - private static string FormatValue(object obj, in HmlSerializerOptions options) + private static PrimitiveValueNode ToPrimitiveValueNode(object obj) { return obj switch { - bool value => value ? "true" : "false", - decimal value => value.ToString(options.CultureInfo), - float value => value.ToString(options.CultureInfo), - double value => value.ToString(options.CultureInfo), - string value => $"'{value}'", - char value => $"'{value}'", - _ => obj.ToString()! + bool value => new BoolValue(value), + + // Pointer numbers + decimal value => new NumberValueNode(value), + double value => new NumberValueNode((decimal) value), + float value => new NumberValueNode((decimal) value), + + // Numbers + long value => new NumberValueNode(value), + ulong value => new NumberValueNode(value), + int value => new NumberValueNode(value), + uint value => new NumberValueNode(value), + nint value => new NumberValueNode(value), + short value => new NumberValueNode(value), + ushort value => new NumberValueNode(value), + sbyte value => new NumberValueNode(value), + byte value => new NumberValueNode(value), + + // Strings + string value => new StringValueNode(value), + char value => new StringValueNode(value), + + // Unknown + _ => new UnknownValueNode(obj) }; } + + private static bool IsPrimitiveType(object obj) + { + var type = obj.GetType(); + return type.IsPrimitive || type.IsEnum || type == typeof(string); + } } diff --git a/src/Hypercube.Utilities/Serialization/Hml/Core/HmlLexer.cs b/src/Hypercube.Utilities/Serialization/Hml/Core/HmlLexer.cs index 02e23cf..b73182c 100644 --- a/src/Hypercube.Utilities/Serialization/Hml/Core/HmlLexer.cs +++ b/src/Hypercube.Utilities/Serialization/Hml/Core/HmlLexer.cs @@ -33,7 +33,7 @@ public static IReadOnlyList Tokenize(string input) var value = match.Value; - if (string.IsNullOrWhiteSpace(value)) + if (string.IsNullOrWhiteSpace(value) && tokenType != TokenType.EndOfLine) continue; tokens.Add(new Token(tokenType, value)); diff --git a/src/Hypercube.Utilities/Serialization/Hml/Core/HmlParser.cs b/src/Hypercube.Utilities/Serialization/Hml/Core/HmlParser.cs index b32f2ef..8774048 100644 --- a/src/Hypercube.Utilities/Serialization/Hml/Core/HmlParser.cs +++ b/src/Hypercube.Utilities/Serialization/Hml/Core/HmlParser.cs @@ -1,32 +1,64 @@ using System.Runtime.CompilerServices; using Hypercube.Utilities.Serialization.Hml.Core.Nodes; using Hypercube.Utilities.Serialization.Hml.Core.Nodes.Value; +using Hypercube.Utilities.Serialization.Hml.Exceptions; +using JetBrains.Annotations; namespace Hypercube.Utilities.Serialization.Hml.Core; -public static class HmlParser +[PublicAPI] +public class HmlParser { - public static IReadOnlyList Parse(IReadOnlyList tokens) + private readonly IReadOnlyList _tokens; + + private INode _context = null!; + private int _position; + + private Token Current => _tokens[_position]; + + public HmlParser(IReadOnlyList tokens) + { + _tokens = tokens; + } + + public void Reset() + { + _context = new RootNode(); + _position = 0; + } + + public RootNode Parse() { - var result = new List(); - var pos = 0; + Reset(); - while (!Match(tokens, ref pos, TokenType.EndOfFile)) - result.Add(ParseDefinition(tokens, ref pos)); + while (!Match(TokenType.EndOfFile)) + { + switch (Current.Type) + { + case TokenType.LBrace: + ParseObject(); + break; + + case TokenType.LBracket: + ParseArray(); + break; + } + } - return result; + throw new HmlException(); } - private static Token Consume(IReadOnlyList tokens, ref int pos, TokenType expected) + private Token Consume(TokenType expected) { - var token = Peek(tokens, ref pos); + var token = Current; if (token.Type != expected) - throw new Exception($"Expected {expected}, got {token.Type}"); - pos++; + throw new HmlException($"Expected {expected}, got {token.Type}"); + + _position++; return token; } - private static DefinitionNode ParseDefinition(IReadOnlyList tokens, ref int pos) + /*private static DefinitionNode ParseDefinition(IReadOnlyList tokens, ref int pos) { var name = Consume(tokens, ref pos, TokenType.Identifier).Value; Consume(tokens, ref pos, TokenType.LAngle); @@ -42,50 +74,60 @@ private static DefinitionNode ParseDefinition(IReadOnlyList tokens, ref i TypeName = typeName, Value = value }; - } + }*/ - private static ValueNode ParseValue(IReadOnlyList tokens, ref int pos) + private IValueNode ParseValue() { - if (Match(tokens, ref pos, TokenType.LBrace)) return ParseObject(tokens, ref pos); - if (Match(tokens, ref pos, TokenType.LBracket)) return ParseArray(tokens, ref pos); - return ParseLiteral(tokens, ref pos); + throw new HmlException(); + + //if (Match(TokenType.LBrace)) return ParseObject(); + //if (Match(TokenType.LBracket)) return ParseArray(); + //return ParseLiteral(tokens, ref pos); + return default!; } - private static ObjectNode ParseObject(IReadOnlyList tokens, ref int pos) + private void ParseObject() { - Consume(tokens, ref pos, TokenType.LBrace); + Consume(TokenType.LBrace); var obj = new ObjectNode(); + obj.SetParent(_context); - while (!Match(tokens, ref pos, TokenType.RBrace)) + _context = obj; + + while (!Match(TokenType.RBrace)) { - var key = Consume(tokens, ref pos, TokenType.Identifier).Value; - Consume(tokens, ref pos, TokenType.Colon); - var val = ParseValue(tokens, ref pos); - obj.Properties[key] = val; + if (!Match(TokenType.String) && !Match(TokenType.Identifier)) + { + + } + var key = Consume(TokenType.Identifier).Value; + Consume(TokenType.Colon); + var val = ParseValue(); + + obj.Add(new KeyValuePairNode(new IdentifierNode(key), val)); } - Consume(tokens, ref pos, TokenType.RBrace); - return obj; + Consume(TokenType.RBrace); } - private static ArrayNode ParseArray(IReadOnlyList tokens, ref int pos) + private ListNode ParseArray() { - Consume(tokens, ref pos, TokenType.LBracket); - var arr = new ArrayNode(); + Consume(TokenType.LBracket); + var arr = new ListNode(); - while (!Match(tokens, ref pos, TokenType.RBracket)) + while (!Match(TokenType.RBracket)) { - var key = Consume(tokens, ref pos, TokenType.Identifier).Value; - Consume(tokens, ref pos, TokenType.Colon); - var val = ParseValue(tokens, ref pos); - arr.Elements.Add(new KeyValuePair(key, val)); + var key = Consume(TokenType.Identifier).Value; + Consume(TokenType.Colon); + var val = ParseValue(); + //arr.Elements.Add(new KeyValuePair(key, val)); } - Consume(tokens, ref pos, TokenType.RBracket); + Consume(TokenType.RBracket); return arr; } - private static LiteralNode ParseLiteral(IReadOnlyList tokens, ref int pos) + /* private static LiteralNode ParseLiteral(IReadOnlyList tokens, ref int pos) { var tok = Peek(tokens, ref pos); if (tok.Type is TokenType.String or TokenType.Number or TokenType.Boolean) @@ -95,23 +137,17 @@ private static LiteralNode ParseLiteral(IReadOnlyList tokens, ref int pos } throw new Exception($"Expected literal, got {tok.Type}"); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Token Peek(IReadOnlyList tokens, ref int pos) - { - return tokens[pos]; - } + }*/ [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Token Next(IReadOnlyList tokens, ref int pos) + private Token Next() { - return tokens[pos++]; + return _tokens[_position++]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool Match(IReadOnlyList tokens, ref int pos, TokenType type) + private bool Match(TokenType type) { - return tokens[pos].Type == type; + return _tokens[_position].Type == type; } } \ No newline at end of file diff --git a/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/DefinitionNode.cs b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/DefinitionNode.cs deleted file mode 100644 index f19bd57..0000000 --- a/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/DefinitionNode.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Hypercube.Utilities.Serialization.Hml.Core.Nodes.Value; - -namespace Hypercube.Utilities.Serialization.Hml.Core.Nodes; - -public class DefinitionNode : Node -{ - public string Name { get; set; } = ""; - public string TypeName { get; set; } = ""; - public ValueNode Value { get; set; } = default!; -} diff --git a/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/IIdentifier.cs b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/IIdentifier.cs new file mode 100644 index 0000000..bbfdd43 --- /dev/null +++ b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/IIdentifier.cs @@ -0,0 +1,6 @@ +namespace Hypercube.Utilities.Serialization.Hml.Core.Nodes; + +public interface IIdentifierNode : INode +{ + string Name { get; } +} diff --git a/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/INode.cs b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/INode.cs new file mode 100644 index 0000000..79df514 --- /dev/null +++ b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/INode.cs @@ -0,0 +1,13 @@ +using System.Text; +using Hypercube.Utilities.Serialization.Hml.Core.CompilerTypes; + +namespace Hypercube.Utilities.Serialization.Hml.Core.Nodes; + +public interface INode +{ + INode Parent { get; } + + void SetParent(INode parent); + void OnBuild(Stack stack, Queue nodes, BuildAstStackFrame frame); + string Render(Stack stack, StringBuilder buffer, RenderAstStackFrame frame, RenderAstState state, HmlSerializerOptions options); +} \ No newline at end of file diff --git a/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/IdentifierNode.cs b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/IdentifierNode.cs new file mode 100644 index 0000000..0647db5 --- /dev/null +++ b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/IdentifierNode.cs @@ -0,0 +1,24 @@ +using System.Text; +using Hypercube.Utilities.Serialization.Hml.Core.CompilerTypes; + +namespace Hypercube.Utilities.Serialization.Hml.Core.Nodes; + +public class IdentifierNode : Node, IIdentifierNode +{ + public string Name { get; } + + public IdentifierNode(string name) + { + Name = name; + } + + public override void OnBuild(Stack stack, Queue nodes, BuildAstStackFrame frame) + { + // Do nothing + } + + public override string Render(Stack stack, StringBuilder buffer, RenderAstStackFrame frame, RenderAstState state, HmlSerializerOptions options) + { + return Name; + } +} \ No newline at end of file diff --git a/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Node.cs b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Node.cs index 4f8569a..4d836f6 100644 --- a/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Node.cs +++ b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Node.cs @@ -1,3 +1,22 @@ +using System.Text; +using Hypercube.Utilities.Serialization.Hml.Core.CompilerTypes; +using Hypercube.Utilities.Serialization.Hml.Exceptions; + namespace Hypercube.Utilities.Serialization.Hml.Core.Nodes; -public abstract class Node; +public abstract class Node : INode +{ + public INode Parent { get; private set; } = null!; + + public void SetParent(INode parent) + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + // if (Parent is not null && Parent != this) + // throw new HmlException(); + + Parent = parent; + } + + public abstract void OnBuild(Stack stack, Queue nodes, BuildAstStackFrame frame); + public abstract string Render(Stack stack, StringBuilder buffer, RenderAstStackFrame frame, RenderAstState state, HmlSerializerOptions options); +} diff --git a/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/RootNode.cs b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/RootNode.cs new file mode 100644 index 0000000..1dd775e --- /dev/null +++ b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/RootNode.cs @@ -0,0 +1,21 @@ +using System.Text; +using Hypercube.Utilities.Serialization.Hml.Core.CompilerTypes; + +namespace Hypercube.Utilities.Serialization.Hml.Core.Nodes; + +public class RootNode : Node +{ + public Node Child = null!; + + public override void OnBuild(Stack stack, Queue nodes, BuildAstStackFrame frame) + { + Child = nodes.Dequeue(); + stack.Push(new BuildAstStackFrame(Child, this)); + } + + public override string Render(Stack stack, StringBuilder buffer, RenderAstStackFrame frame, RenderAstState state, HmlSerializerOptions options) + { + stack.Push(new RenderAstStackFrame(Child)); + return string.Empty; + } +} diff --git a/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/ArrayNode.cs b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/ArrayNode.cs deleted file mode 100644 index 776651d..0000000 --- a/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/ArrayNode.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Hypercube.Utilities.Serialization.Hml.Core.Nodes.Value; - -public class ArrayNode : ValueNode -{ - public List> Elements { get; set; } = []; -} diff --git a/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/EndNode.cs b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/EndNode.cs new file mode 100644 index 0000000..ac4669e --- /dev/null +++ b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/EndNode.cs @@ -0,0 +1,17 @@ +using System.Text; +using Hypercube.Utilities.Serialization.Hml.Core.CompilerTypes; + +namespace Hypercube.Utilities.Serialization.Hml.Core.Nodes.Value; + +public class EndNode : Node +{ + public override void OnBuild(Stack stack, Queue nodes, BuildAstStackFrame frame) + { + // Do nothing + } + + public override string Render(Stack stack, StringBuilder buffer, RenderAstStackFrame frame, RenderAstState state, HmlSerializerOptions options) + { + return string.Empty; + } +} \ No newline at end of file diff --git a/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/Expression/BinaryExpressionNode.cs b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/Expression/BinaryExpressionNode.cs index 2f91840..4596a47 100644 --- a/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/Expression/BinaryExpressionNode.cs +++ b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/Expression/BinaryExpressionNode.cs @@ -1,8 +1,21 @@ -namespace Hypercube.Utilities.Serialization.Hml.Core.Nodes.Value.Expression; +using System.Text; +using Hypercube.Utilities.Serialization.Hml.Core.CompilerTypes; +namespace Hypercube.Utilities.Serialization.Hml.Core.Nodes.Value.Expression; +/* public class BinaryExpressionNode : ExpressionNode { - public ValueNode Left { get; set; } = default!; + public IValueNode Left { get; set; } = null!; public string Operator { get; set; } = ""; - public ValueNode Right { get; set; } = default!; + public IValueNode Right { get; set; } = null!; + public override void OnBuild(Stack stack, Queue nodes, BuildAstStackFrame frame) + { + + } + + public override string Render(Stack stack, StringBuilder buffer, RenderAstStackFrame frame, RenderAstState state, HmlSerializerOptions options) + { + return string.Empty; + } } +*/ \ No newline at end of file diff --git a/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/Expression/ExpressionNode.cs b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/Expression/ExpressionNode.cs index 1f3d0af..1ab6465 100644 --- a/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/Expression/ExpressionNode.cs +++ b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/Expression/ExpressionNode.cs @@ -1,3 +1,3 @@ namespace Hypercube.Utilities.Serialization.Hml.Core.Nodes.Value.Expression; -public abstract class ExpressionNode : ValueNode; +//public abstract class ExpressionNode : IValueNode; diff --git a/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/ValueNode.cs b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/IValueNode.cs similarity index 62% rename from src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/ValueNode.cs rename to src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/IValueNode.cs index 557b5a2..abcd1bc 100644 --- a/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/ValueNode.cs +++ b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/IValueNode.cs @@ -1,3 +1,3 @@ namespace Hypercube.Utilities.Serialization.Hml.Core.Nodes.Value; -public abstract class ValueNode : Node; +public interface IValueNode : INode; \ No newline at end of file diff --git a/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/KeyValuePair.cs b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/KeyValuePair.cs new file mode 100644 index 0000000..6212ab1 --- /dev/null +++ b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/KeyValuePair.cs @@ -0,0 +1,71 @@ +using System.Text; +using Hypercube.Utilities.Serialization.Hml.Core.CompilerTypes; + +namespace Hypercube.Utilities.Serialization.Hml.Core.Nodes.Value; + +public class KeyValuePairNode : Node +{ + public IIdentifierNode Key; + public IValueNode Value; + + public KeyValuePairNode() + { + Key = null!; + Value = null!; + } + + public KeyValuePairNode(IIdentifierNode key, IValueNode value) + { + Key = key; + Value = value; + + key.SetParent(this); + Value.SetParent(this); + } + + public override void OnBuild(Stack stack, Queue nodes, BuildAstStackFrame frame) + { + var node = nodes.Dequeue(); + + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (Key is null) + { + if (node is not IIdentifierNode keyNode) + throw new Exception(""); + + stack.Push(frame); + stack.Push(new BuildAstStackFrame(node, this)); + Key = keyNode; + return; + } + + if (node is not IValueNode valueNode) + throw new Exception(""); + + Value = valueNode; + stack.Push(new BuildAstStackFrame(node, this)); + } + + public override string Render(Stack stack, StringBuilder buffer, RenderAstStackFrame frame, RenderAstState state, HmlSerializerOptions options) + { + if (frame.State == 0) + { + stack.Push(frame with { State = 1 }); + stack.Push(new RenderAstStackFrame(Key)); + return string.Empty; + } + + if (frame.State == 1) + { + buffer.Append(":"); + + if (options.Indented) + buffer.Append(" "); + + stack.Push(new RenderAstStackFrame(Value)); + return string.Empty; + } + + return string.Empty; + } +} \ No newline at end of file diff --git a/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/ListNode.cs b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/ListNode.cs new file mode 100644 index 0000000..9bd9b29 --- /dev/null +++ b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/ListNode.cs @@ -0,0 +1,89 @@ +using System.Text; +using Hypercube.Utilities.Serialization.Hml.Core.CompilerTypes; + +namespace Hypercube.Utilities.Serialization.Hml.Core.Nodes.Value; + +public sealed class ListNode : Node, IValueNode +{ + public List Elements { get; } = []; + + public override void OnBuild(Stack stack, Queue nodes, BuildAstStackFrame frame) + { + var nextNode = nodes.Dequeue(); + + if (nextNode is EndNode) + return; + + if (nextNode is not IValueNode valueNode) + throw new Exception(""); + + Elements.Add(valueNode); + stack.Push(frame); + stack.Push(new BuildAstStackFrame(valueNode, this)); + } + + public override string Render(Stack stack, StringBuilder buffer, RenderAstStackFrame frame, RenderAstState state, HmlSerializerOptions options) + { + if (frame.State == 0) + { + buffer.Append('['); + state.PushIndent(); + + if (Elements.Count == 0) + { + buffer.Append(']'); + state.PopIndent(); + return string.Empty; + } + + if (options.Indented) + { + buffer.AppendLine(); + buffer.Append(state.Indent); + } + + stack.Push(frame with { State = 2 }); + if (Elements.Count > 1) + stack.Push(frame with { State = 1, Index = 1}); + + stack.Push(new RenderAstStackFrame(Elements[0])); + return string.Empty; + } + + if (frame.State == 1) + { + if (frame.Index < Elements.Count) + { + var index = frame.Index; + + if (options.Eol || !options.Indented) + buffer.Append(';'); + + if (options.Indented) + { + buffer.AppendLine(); + buffer.Append(state.Indent); + } + + stack.Push(frame with { State = 1, Index = index + 1 }); + stack.Push(new RenderAstStackFrame(Elements[index])); + } + + return string.Empty; + } + + if (frame.State == 2) + { + if (options.Indented) + { + buffer.AppendLine(); + state.PopIndent(); + buffer.Append(state.Indent); + } + buffer.Append(']'); + return string.Empty; + } + + return string.Empty; + } +} \ No newline at end of file diff --git a/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/LiteralNode.cs b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/LiteralNode.cs deleted file mode 100644 index d7f52aa..0000000 --- a/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/LiteralNode.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Hypercube.Utilities.Serialization.Hml.Core.Nodes.Value; - -public class LiteralNode : ValueNode -{ - public string Value { get; set; } = ""; -} diff --git a/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/ObjectNode.cs b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/ObjectNode.cs index 4bbc670..eb3b5fe 100644 --- a/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/ObjectNode.cs +++ b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/ObjectNode.cs @@ -1,6 +1,95 @@ +using System.Text; +using Hypercube.Utilities.Serialization.Hml.Core.CompilerTypes; + namespace Hypercube.Utilities.Serialization.Hml.Core.Nodes.Value; -public class ObjectNode : ValueNode +public class ObjectNode : Node, IValueNode { - public Dictionary Properties { get; set; } = new(); + public List Properties { get; } = []; + + public void Add(KeyValuePairNode node) + { + Properties.Add(node); + node.SetParent(this); + } + + public override void OnBuild(Stack stack, Queue nodes, BuildAstStackFrame frame) + { + var nextNode = nodes.Dequeue(); + + if (nextNode is EndNode) + return; + + if (nextNode is not KeyValuePairNode keyValuePair) + throw new Exception(""); + + Properties.Add(keyValuePair); + stack.Push(frame); + stack.Push(new BuildAstStackFrame(keyValuePair, this)); + } + + public override string Render(Stack stack, StringBuilder buffer, RenderAstStackFrame frame, RenderAstState state, HmlSerializerOptions options) + { + if (frame.State == 0) + { + buffer.Append('{'); + state.PushIndent(); + + if (options.Indented && Properties.Count != 0) + { + buffer.AppendLine(); + buffer.Append(state.Indent); + } + + if (Properties.Count == 0) + { + buffer.Append('}'); + state.PopIndent(); + return string.Empty; + } + + stack.Push(frame with { State = 2 }); + if (Properties.Count > 1) + stack.Push(frame with { State = 1, Index = 1 }); + + stack.Push(new RenderAstStackFrame(Properties[0])); + return string.Empty; + } + + if (frame.State == 1) + { + if (frame.Index < Properties.Count) + { + var index = frame.Index; + + if (options.Eol || !options.Indented) + buffer.Append(';'); + + if (options.Indented) + { + buffer.AppendLine(); + buffer.Append(state.Indent); + } + + stack.Push(frame with { State = 1, Index = index + 1 }); + stack.Push(new RenderAstStackFrame(Properties[index])); + } + + return string.Empty; + } + + if (frame.State == 2) + { + if (options.Indented) + { + buffer.AppendLine(); + state.PopIndent(); + buffer.Append(state.Indent); + } + buffer.Append('}'); + return string.Empty; + } + + return string.Empty; + } } diff --git a/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/PrimitiveValueNode.cs b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/PrimitiveValueNode.cs new file mode 100644 index 0000000..ee805bb --- /dev/null +++ b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/PrimitiveValueNode.cs @@ -0,0 +1,11 @@ +using Hypercube.Utilities.Serialization.Hml.Core.CompilerTypes; + +namespace Hypercube.Utilities.Serialization.Hml.Core.Nodes.Value; + +public abstract class PrimitiveValueNode : Node, IValueNode +{ + public override void OnBuild(Stack stack, Queue nodes, BuildAstStackFrame frame) + { + // Do nothing + } +} \ No newline at end of file diff --git a/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/Primitives/BoolValue.cs b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/Primitives/BoolValue.cs new file mode 100644 index 0000000..545abfa --- /dev/null +++ b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/Primitives/BoolValue.cs @@ -0,0 +1,19 @@ +using System.Text; +using Hypercube.Utilities.Serialization.Hml.Core.CompilerTypes; + +namespace Hypercube.Utilities.Serialization.Hml.Core.Nodes.Value.Primitives; + +public class BoolValue : PrimitiveValueNode +{ + private readonly bool _value; + + public BoolValue(bool value) + { + _value = value; + } + + public override string Render(Stack stack, StringBuilder buffer, RenderAstStackFrame frame, RenderAstState state, HmlSerializerOptions options) + { + return _value.ToString(); + } +} \ No newline at end of file diff --git a/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/Primitives/NullValueNode.cs b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/Primitives/NullValueNode.cs new file mode 100644 index 0000000..81585bf --- /dev/null +++ b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/Primitives/NullValueNode.cs @@ -0,0 +1,12 @@ +using System.Text; +using Hypercube.Utilities.Serialization.Hml.Core.CompilerTypes; + +namespace Hypercube.Utilities.Serialization.Hml.Core.Nodes.Value.Primitives; + +public class NullValueNode : PrimitiveValueNode +{ + public override string Render(Stack stack, StringBuilder buffer, RenderAstStackFrame frame, RenderAstState state, HmlSerializerOptions options) + { + return "null"; + } +} \ No newline at end of file diff --git a/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/Primitives/NumberValueNode.cs b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/Primitives/NumberValueNode.cs new file mode 100644 index 0000000..3f6ceb4 --- /dev/null +++ b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/Primitives/NumberValueNode.cs @@ -0,0 +1,19 @@ +using System.Text; +using Hypercube.Utilities.Serialization.Hml.Core.CompilerTypes; + +namespace Hypercube.Utilities.Serialization.Hml.Core.Nodes.Value.Primitives; + +public class NumberValueNode : PrimitiveValueNode +{ + public readonly decimal Value; + + public NumberValueNode(decimal value) + { + Value = value; + } + + public override string Render(Stack stack, StringBuilder buffer, RenderAstStackFrame frame, RenderAstState state, HmlSerializerOptions options) + { + return Value.ToString(options.CultureInfo); + } +} diff --git a/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/Primitives/StringValueNode.cs b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/Primitives/StringValueNode.cs new file mode 100644 index 0000000..789d701 --- /dev/null +++ b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/Primitives/StringValueNode.cs @@ -0,0 +1,26 @@ +using System.Text; +using Hypercube.Utilities.Serialization.Hml.Core.CompilerTypes; + +namespace Hypercube.Utilities.Serialization.Hml.Core.Nodes.Value.Primitives; + +public class StringValueNode : PrimitiveValueNode, IIdentifierNode +{ + public readonly string Value; + + public string Name => Value; + + public StringValueNode(string value) + { + Value = value; + } + + public StringValueNode(char value) + { + Value = value.ToString(); + } + + public override string Render(Stack stack, StringBuilder buffer, RenderAstStackFrame frame, RenderAstState state, HmlSerializerOptions options) + { + return $"'{Value}'"; + } +} diff --git a/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/Primitives/UnknownValueNode.cs b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/Primitives/UnknownValueNode.cs new file mode 100644 index 0000000..c6404b9 --- /dev/null +++ b/src/Hypercube.Utilities/Serialization/Hml/Core/Nodes/Value/Primitives/UnknownValueNode.cs @@ -0,0 +1,19 @@ +using System.Text; +using Hypercube.Utilities.Serialization.Hml.Core.CompilerTypes; + +namespace Hypercube.Utilities.Serialization.Hml.Core.Nodes.Value.Primitives; + +public class UnknownValueNode : PrimitiveValueNode +{ + public readonly object Value; + + public UnknownValueNode(object value) + { + Value = value; + } + + public override string Render(Stack stack, StringBuilder buffer, RenderAstStackFrame frame, RenderAstState state, HmlSerializerOptions options) + { + return Value.ToString() ?? string.Empty; + } +} \ No newline at end of file diff --git a/src/Hypercube.Utilities/Serialization/Hml/Core/Token.cs b/src/Hypercube.Utilities/Serialization/Hml/Core/Token.cs index 3f70640..26b481a 100644 --- a/src/Hypercube.Utilities/Serialization/Hml/Core/Token.cs +++ b/src/Hypercube.Utilities/Serialization/Hml/Core/Token.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.Runtime.CompilerServices; namespace Hypercube.Utilities.Serialization.Hml.Core; @@ -13,4 +14,20 @@ public Token(TokenType type, string value) Type = type; Value = value; } + + public override string ToString() + { + return $"{Type}: {ValueFormat()}"; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ValueFormat() + { + return Type switch + { + TokenType.EndOfLine => @"\r\n", + TokenType.EndOfFile => @"\z", + _ => Value + }; + } } diff --git a/src/Hypercube.Utilities/Serialization/Hml/Core/TokenType.cs b/src/Hypercube.Utilities/Serialization/Hml/Core/TokenType.cs index f775cfc..170f172 100644 --- a/src/Hypercube.Utilities/Serialization/Hml/Core/TokenType.cs +++ b/src/Hypercube.Utilities/Serialization/Hml/Core/TokenType.cs @@ -2,24 +2,37 @@ namespace Hypercube.Utilities.Serialization.Hml.Core; public enum TokenType : short { + // Fields Identifier, + + // Primitives Number, + Boolean, String, + + // Symbols Colon, Equal, + Semicolon, + Comma, + Dollar, + + // Groups LBracket, RBracket, LParen, RParen, - Comma, - Comment, - Indent, - Dedent, - EndOfFile, LBrace, RBrace, - Boolean, - Char, LAngle, - RAngle + RAngle, + + // Comments + Comment, + LComment, + RComment, + + // Ends + EndOfFile, + EndOfLine } diff --git a/src/Hypercube.Utilities/Serialization/Hml/Core/Tokens.cs b/src/Hypercube.Utilities/Serialization/Hml/Core/Tokens.cs index d88189c..1daf205 100644 --- a/src/Hypercube.Utilities/Serialization/Hml/Core/Tokens.cs +++ b/src/Hypercube.Utilities/Serialization/Hml/Core/Tokens.cs @@ -4,16 +4,26 @@ public static class Tokens { public static readonly Dictionary TokenRegexs = new() { + // Ends + { TokenType.EndOfLine, @"\r?\n" }, + { TokenType.EndOfFile, @"\z" }, + + // Fields + { TokenType.Identifier, @"\b[a-zA-Z_!][a-zA-Z0-9_!]*\b|!" }, + + // Primitives { TokenType.Number, @"\d+(\.\d+)?" }, { TokenType.Boolean, @"\b(true|false)\b" }, + { TokenType.String, """(?:"([^"\\\r\n]|\\.)*"|'([^'\\\r\n]|\\.)*')""" }, - { TokenType.String, "\"(?(?:[^\"\\\\]|\\\\.)*)\"" }, - { TokenType.Char, @"'(?(?:[^'\\]|\\.))'" }, - - { TokenType.Identifier, @"\b[a-zA-Z_!][a-zA-Z0-9_!]*\b|!" }, - + // Symbols { TokenType.Colon, ":" }, { TokenType.Equal, "=" }, + { TokenType.Semicolon, ";" }, + { TokenType.Comma, "," }, + { TokenType.Dollar, @"\$" }, + + // Groups { TokenType.LAngle, @"\<" }, { TokenType.RAngle, @"\>" }, { TokenType.LBracket, @"\[" }, @@ -22,13 +32,10 @@ public static class Tokens { TokenType.RParen, @"\)" }, { TokenType.LBrace, @"\{" }, { TokenType.RBrace, @"\}" }, - { TokenType.Comma, "," }, - - { TokenType.Comment, "#.*" }, - - { TokenType.Indent, @"(?<=\n)([ \t]+)(?=\S)" }, - { TokenType.Dedent, @"(?<=\n)(?=\S)" }, - { TokenType.EndOfFile, @"\z" } + // Comments + { TokenType.Comment, @"//[^\r\n]*" }, + { TokenType.LComment, "//*" }, + { TokenType.RComment, @"\*/" }, }; } diff --git a/src/Hypercube.Utilities/Serialization/Hml/Exceptions/HmlException.cs b/src/Hypercube.Utilities/Serialization/Hml/Exceptions/HmlException.cs new file mode 100644 index 0000000..162f1fb --- /dev/null +++ b/src/Hypercube.Utilities/Serialization/Hml/Exceptions/HmlException.cs @@ -0,0 +1,12 @@ +namespace Hypercube.Utilities.Serialization.Hml.Exceptions; + +public class HmlException : Exception +{ + public HmlException() + { + } + + public HmlException(string? message) : base(message) + { + } +} diff --git a/src/Hypercube.Utilities/Serialization/Hml/HmlSerializer.cs b/src/Hypercube.Utilities/Serialization/Hml/HmlSerializer.cs index dbb4070..e623865 100644 --- a/src/Hypercube.Utilities/Serialization/Hml/HmlSerializer.cs +++ b/src/Hypercube.Utilities/Serialization/Hml/HmlSerializer.cs @@ -2,12 +2,11 @@ namespace Hypercube.Utilities.Serialization.Hml; -public sealed class HmlSerializer +public static class HmlSerializer { public static string Serialize(object obj, HmlSerializerOptions? options = null) { - options ??= new HmlSerializerOptions(); - return HmlCompiler.Serialize(obj, (HmlSerializerOptions) options).ToString(); + return HmlCompiler.Serialize(obj, options ?? new HmlSerializerOptions()); } public static object Deserialize(string content, HmlSerializerOptions? options = null) @@ -17,10 +16,10 @@ public static object Deserialize(string content, HmlSerializerOptions? options = public static T Deserialize(string content, HmlSerializerOptions? options = null) { - options ??= new HmlSerializerOptions(); + /*options ??= new HmlSerializerOptions(); var tokens = HmlLexer.Tokenize(content); - var ast = HmlParser.Parse(tokens); + var ast = HmlParser.Parse(tokens);*/ return default!; //compiler.Compile(ast, options); } diff --git a/src/Hypercube.Utilities/Serialization/Hml/HmlSerializerOptions.cs b/src/Hypercube.Utilities/Serialization/Hml/HmlSerializerOptions.cs index 4632f59..a80c70a 100644 --- a/src/Hypercube.Utilities/Serialization/Hml/HmlSerializerOptions.cs +++ b/src/Hypercube.Utilities/Serialization/Hml/HmlSerializerOptions.cs @@ -2,13 +2,10 @@ namespace Hypercube.Utilities.Serialization.Hml; -public readonly struct HmlSerializerOptions +public record HmlSerializerOptions { public CultureInfo CultureInfo { get; init; } = CultureInfo.InvariantCulture; - public bool TrailingComma { get; init; } = true; + public bool Eol { get; init; } = false; + public bool Indented { get; init; } public int IndentSize { get; init; } = 2; - - public HmlSerializerOptions() - { - } -} \ No newline at end of file +} diff --git a/src/Hypercube.Utilities/Serialization/Hml/HmlSpecification.md b/src/Hypercube.Utilities/Serialization/Hml/HmlSpecification.md deleted file mode 100644 index f0113fb..0000000 --- a/src/Hypercube.Utilities/Serialization/Hml/HmlSpecification.md +++ /dev/null @@ -1,3 +0,0 @@ -# Hypercube Markup Language (.hml format) - -## Overview diff --git a/src/Hypercube.Utilities/Serialization/Hml/HmlSpecificationRU.md b/src/Hypercube.Utilities/Serialization/Hml/HmlSpecificationRU.md new file mode 100644 index 0000000..eb2b3c8 --- /dev/null +++ b/src/Hypercube.Utilities/Serialization/Hml/HmlSpecificationRU.md @@ -0,0 +1,329 @@ +# **Обзор** +**HML (Hypercube Markup Language)** — декларативный язык описания данных, основанный на минималистичном синтаксисе и строгой типизации. +Он предназначен для сериализации и обмена структурированными данными в формате, удобном как для человека, так и для машины. + +# **Конструкции** + +## **Идентификатор** + +Идентификатор — символьная последовательность, предназначенная для обозначения элемента, поля или свойства объекта. + +### **Правила** +1. Первый символ **не может быть цифрой**. +2. Идентификаторы **чувствительны к регистру**. +3. Запрещено использовать **зарезервированные символы** (`{}`, `[]`, `:`, `;`, `$`, `"`, и т. д.) в качестве части идентификатора. + +### **Примеры** +```yaml +name +UserID +_config +HelloWorld +``` + +## **Значение** +Значением может быть **любой допустимый тип данных**, включая примитивы, коллекции и специальные конструкции. +Значение присваивается идентификатору с помощью двоеточия `:`. +Значение должно заканчиваться точкой с запятой `;`, либо переносом строки. + +```yaml +field1: "Hello" +field2: 123; +``` + +# **Типы данных** + +## **Примитивы** + +### **Число (Number)** +Представляет целые и вещественные числа в различных системах счисления. + +**Целые числа:** `100`, `189`
+**Вещественные числа:** `1.0`, `10.52`
+**Двоичная запись:** `0b1` (равносильная запись `1`), `0b1011` (равносильная запись `11`)
+**Шеснацеричная запись:** `0xF5B6` (равносильная запись `62902`)
+**Экспоненциальная запись:** `1.5e-4` (равносильная запись `0.00015`), `1e3` (равносильная запись `1000`) + +### **Строка (String)** + +Представляет последовательность символов в двойных или одинарных кавычках `"..."` или многострочный текст в тройных `"""..."""`. + +**Обычная строка:** `"Hello"`, `'Hypercube'`
+**Многострочная строка:** +```yaml +""" +First line +Second line +""" +``` + +### **Булево значение (Boolean)** +Представляет логическое состояние. + +```yaml +true +false +``` + +### **Отсутствие значения (Null)** +Представляет отсутствие значения. +Часто используется при сериализации шаблонов. + +```yaml +null +``` + +### **Перечисление (Enum)** +Перечисления используются для представления **предопределённых констант**, +которые задаются заранее разработчиком при работе с сериализатором. +Встроенных перечислений в HML нет. + +**Синтаксис:** `$ИмяПеречисления.Элемент` + +**Примеры:** +```yaml +$LogLevel.Info +$Color.Red +``` + +**Особенности:** +1. Перечисления могут быть любым типом данных, в зависимости от того, как их определил разработчик. +2. Над числовыми перечислениями разрешены все стандартные математические и побитовые операции: + +```yaml +$Flags.Read | $Flags.Write // Побитовое ИЛИ +$Flags.Execute & $Flags.All // Побитовое И +$Level.High + 2 // Арифметика +``` + +3. Значения перечислений всегда фиксируются сериализатором и не меняются динамически во время выполнения HML-файла. + +## **Коллекции** + +### **Объект (Object)** +Набор уникальных пар *идентификатор–значение* в фигурных скобках `{}`. +Каждое поле отделяется точкой с запятой `;`. + +**Пример многострочной записи объекта:** +```yaml +{ + name: "Hypercube" + version: 1.0 + stable: true +} +``` + +**Пример однострочной записи объекта:** +```yaml +{ name: 'Hypercube'; version: '1.0'; stable: true } +``` + + +### **Словарь (Dictionary)** +Коллекция произвольных пар *ключ–значение*, где и ключ, и значение могут быть любого типа. +Каждая пара отделяется точкой с запятой `;`. +Ключи уникальны в пределах словаря. + +> [!IMPORTANT] +> *Прмиерчание:* ключ может быть оформлен как индефикатор, +> но в контексте словаря, данная запись будет эквивалента типу строка. + +**Пример многострочной записи словаря:** +```yaml +[ + 'Hi': 'World' + 1: 0 + true: false // равносильно 'true': false + null: null // равносильно 'null': null + $LogLevel.Info: 'Informational' +] +``` + +**Пример однострочной записи объекта:** +```yaml +[ 'Hi': 'World'; 1: 0; true: false; null: null; $LogLevel.Info: 'Informational' ] +``` + +### **Список (List)** +Упорядоченная коллекция значений. +Элементы отделяются точкой с запятой `;`. + +**Пример многострочной записи списка:** +```yaml +[ + 'World' + 0 + false + null + [ + 'key1' + 'ac' + ] + ['key2'; 'ab'] +] +``` + +**Пример однострочной записи объекта:** +```yaml +[ 'World'; 0; false; null; [ 'key1'; 'ac' ]; ['key2'; 'ab'] ] +``` + +## **Специальные типы** + +### **Неизвестность (Unknown)** +Обозначает отсутствие известного типа данных или недоопределённость структуры. +Может возникать при вводе нераспознанных символов, не принадлежащих никакому типу. + +**Примеры:** + +```yaml +Unknown? +??? +Hello World! +?123!; +``` + + +# **Ключевые слова и символы** + +## **Ключевые слова** +В HML есть несколько зарезервированных слов, которые имеют специальное значение и **могут** использоваться как идентификаторы: + +| Слово | Значение | +| --------- | ------------------------ | +| `true` | Булево значение "истина" | +| `false` | Булево значение "ложь" | +| `null` | Отсутствие значения | +| `unknown` | Неизвестный тип данных | + + +## **Зарезервированные символы** +Следующие символы нельзя использовать в идентификаторах, так как они служат для синтаксиса HML: + +| Символ | Значение | +| ------ | -------------------------------------- | +| `-` | Разделитель/специальный символ | +| `{` | Начало объекта | +| `}` | Конец объекта | +| `[` | Начало списка/словаря | +| `]` | Конец списка/словаря | +| `<` | Начало определителя типа | +| `>` | Конец определителя типа | +| `$` | Обозначение использования перечисления | +| `'` | Ограничитель строк | +| `"` | Ограничитель строк | +| `(` | Начало выражения | +| `)` | Конец выражения | + +Эти символы можно использовать только в контексте их синтаксической роли. + +## **Коментарии** +В HML поддерживаются однострочные и многострочные комментарии: + +| Выражение | Тип комментария | +| --------- | --------------------------------- | +| `//` | Однострочный комментарий | +| `/*` | Начало многострочного комментария | +| `*/` | Конец многострочного комментария | + +## **Математические операции** +В HML поддерживаются стандартные арифметические операции над числами: + +| Символ | Значение | Пример | +| ------ | -------------------- | -------------- | +| `+` | Сложение | `5 + 3` → `8` | +| `-` | Вычитание | `5 - 3` → `2` | +| `*` | Умножение | `5 * 3` → `15` | +| `/` | Деление | `6 / 3` → `2` | +| `%` | Остаток от деления | `5 % 3` → `2` | +| `**` | Возведение в степень | `2 ** 3` → `8` | + + +## **Побитовые операции** +Побитовые операции работают с числами на уровне их бинарного представления: + +| Символ | Значение | Пример | +| ------ | ------------------------- | ------------------------- | +| `&` | Побитовое И (AND) | `5 & 3` → `1` | +| `\|` | Побитовое ИЛИ (OR) | `5 \| 3` → `7` | +| `^` | Побитовое Искл. ИЛИ (XOR) | `5 ^ 3` → `6` | +| `~` | Побитовое НЕ (NOT) | `~5` → `-6` | +| `<<` | Сдвиг влево | `5 << 1` → `10` | +| `>>` | Сдвиг вправо | `5 >> 1` → `2` | +| `>>>` | Логический сдвиг вправо | `-5 >>> 1` → `2147483645` | + +# Математические и логические выражения +В HML поддержка математических и логических выражений реализована на уровне препроцессора, +а не самого интерпретатора языка. +Это значит, что перед тем как HML-файл будет десериализован, +все арифметические и побитовые операции вычисляются заранее, +и результат подставляется в структуру данных. + +Например, запись: +```yaml +offset: 1 / 10 * 2, 5 +``` + +Cначала проходит через препроцессор +и результат выражение подставляется на место операции: +```yaml +offset: 0.2, 5 +``` + +После этого десериализатор видит невыражаемую в стандартных типах структуру (в данном случае `0.2, 5`) +и присваивает ей тип `unknown`, сохраняя фактическое значение как строку. +Так, что в значение типа `unknown` попадет результат выражения, а не само выражение. + +# Явное указание типа +HML поддерживает конструкцию ``, которая позволяет явно указать тип значения. + +**Синтаксис:** `<тип> значение` + +**Описание:** +- Тип — имя типа, используемого для интерпретации значения. +- Значение — данные, которые необходимо интерпретировать как указанный тип. +- Если значение не соответствует типу, оно сохраняется как `unknown` с явной меткой типа. + +**Примеры:** +```yaml +num: 1 / 10 * 2 // Явное указание не требуется т.к. 0.2 уже тип данных number +shape: { size: 10, 10 } // Тип будет unknown с меткой и значением объекта +color: 0xffffff // Тип будет unknown с меткой и значением 16777215 +``` + +# Комплексный пример + +``` +/* +* Типов данных Vector2 и Color по умолчанию не сущществует в HML +* При десириализации вы получите unknown с значием ввиде строки +* С целью потдержки своих типов и был создан unknown +*/ +{ + type: entity + name: test entity + components: [ + transfrom: { + position: 10, 5 // Значение поля будет unknown ('10, 5') + scale: 1, 1 + } + rigibody2d: { + layer: $Layers.Imppassible | $Layers.Opaque + shape: { + size: 10, 10 + } + } + sprite: { + texture: '/resources/textures/test.png' + color: #ffffff + + // Значение поля будет unknown ('0.1875, -0.3125') + offset: 1 / 16 * 3, 1 / 16 * -5 + } + damage: { value: 5; type: $DamageType.Fire } + ability: { + key: 'A' + } + ] +} +``` \ No newline at end of file