diff --git a/YangParser/SemanticModel/Action.cs b/YangParser/SemanticModel/Action.cs index 5fc8e26..13c6bfa 100644 --- a/YangParser/SemanticModel/Action.cs +++ b/YangParser/SemanticModel/Action.cs @@ -70,6 +70,7 @@ public override string ToCode() await WriteXML(writer); await writer.WriteEndElementAsync(); await writer.WriteEndElementAsync(); + await writer.FlushAsync(); var xml = stringBuilder.ToString(); """); builder.AppendLine(returnType != "Task" @@ -135,7 +136,7 @@ public string WriteCall return $$""" if({{MakeName(Argument)}}InputValue is not null) { - await writer.WriteStartElementAsync(null,"{{Source.Argument}}",null); + await writer.WriteStartElementAsync({{xmlPrefix}},"{{Source.Argument}}",{{xmlNs}}); await {{MakeName(Argument)}}InputValue.WriteXML(writer); await writer.WriteEndElementAsync(); } diff --git a/YangParser/SemanticModel/AnyData.cs b/YangParser/SemanticModel/AnyData.cs index 6238e76..082f4b3 100644 --- a/YangParser/SemanticModel/AnyData.cs +++ b/YangParser/SemanticModel/AnyData.cs @@ -35,11 +35,10 @@ public string WriteCall { get { - var pre = string.IsNullOrWhiteSpace(Prefix) ? "null" : $"\"{Prefix}\""; return $$""" if({{TargetName}} != null) { - await writer.WriteStartElementAsync({{pre}},"{{Argument}}",null); + await writer.WriteStartElementAsync({{xmlPrefix}},"{{Argument}}",{{xmlNs}}); await writer.WriteStringAsync({{TargetName}}); await writer.WriteEndElementAsync(); } diff --git a/YangParser/SemanticModel/AnyXml.cs b/YangParser/SemanticModel/AnyXml.cs index a072b41..6ccc242 100644 --- a/YangParser/SemanticModel/AnyXml.cs +++ b/YangParser/SemanticModel/AnyXml.cs @@ -30,18 +30,17 @@ public override string ToCode() { return $"public string? {TargetName} {{ get; set; }}"; } - + public string TargetName => MakeName(Argument); public string WriteCall { get { - var pre = string.IsNullOrWhiteSpace(Prefix) ? "null" : $"\"{Prefix}\""; return $$""" if({{TargetName}} != null) { - await writer.WriteStartElementAsync({{pre}},"{{Argument}}",null); + await writer.WriteStartElementAsync({{xmlPrefix}},"{{Argument}}",{{xmlNs}}); await writer.WriteStringAsync({{TargetName}}); await writer.WriteEndElementAsync(); } diff --git a/YangParser/SemanticModel/Builtins/Union.cs b/YangParser/SemanticModel/Builtins/Union.cs index f1640d7..185dcd5 100644 --- a/YangParser/SemanticModel/Builtins/Union.cs +++ b/YangParser/SemanticModel/Builtins/Union.cs @@ -32,6 +32,11 @@ public class {{name}} {{Statement.Indent(string.Join("\n", types.Select(typeName => $"public static implicit operator {typeName}?({name}? input) => input is null ? null : input.{varName(typeName)} ?? throw new InvalidOperationException(\"Union was not of effective type '{typeName}'\");")))}} {{Statement.Indent(string.Join("\n", types.Select(typeName => $"public static implicit operator {name}({typeName} input) => new {name}(input);")))}} {{Statement.Indent(string.Join("\n", declarations))}} + public override string? ToString() + { + {{Statement.Indent(Statement.Indent(string.Join("\n", types.Select(typeName => $"if({varName(typeName)} is not null) return {varName(typeName)}.ToString();"))))}} + return string.Empty; + } } """; return (name, definition); diff --git a/YangParser/SemanticModel/CompilationUnit.cs b/YangParser/SemanticModel/CompilationUnit.cs index a9647db..f258b1d 100644 --- a/YangParser/SemanticModel/CompilationUnit.cs +++ b/YangParser/SemanticModel/CompilationUnit.cs @@ -11,11 +11,11 @@ public CompilationUnit(Module[] modules, string Namespace = "Somewhere") : base( string.Empty, [], new Metadata(string.Empty, new Parser.Position(), 0))) { - this.Namespace = Namespace; + this.MyNamespace = Namespace; Children = modules; } - public string Namespace { get; set; } + public string MyNamespace { get; set; } public override ChildRule[] PermittedChildren { get; } = [ @@ -27,7 +27,7 @@ public override string ToCode() var members = new List(); foreach (var module in Children.OfType()) { - var typeName = module.Namespace.Substring(0, module.Namespace.Length - 1); + var typeName = module.MyNamespace.Substring(0, module.MyNamespace.Length - 1); var memberName = MakeName(module.Argument); members.Add($"public {typeName}? {memberName} {{ get; set; }}"); } @@ -36,9 +36,9 @@ public override string ToCode() using System; using System.Xml; using Yang.Attributes; - namespace {{Namespace}}; + namespace {{MyNamespace}}; /// - ///Configuration root object for {{Namespace}} based on provided .yang modules + ///Configuration root object for {{MyNamespace}} based on provided .yang modules ///{{AttributeString}} public class Configuration { diff --git a/YangParser/SemanticModel/IStatement.cs b/YangParser/SemanticModel/IStatement.cs index 0b5d42b..53c7d69 100644 --- a/YangParser/SemanticModel/IStatement.cs +++ b/YangParser/SemanticModel/IStatement.cs @@ -21,6 +21,7 @@ public interface IStatement string XPath { get; } (string Namespace, string Prefix)? XmlNamespace { get; set; } string Prefix { get; } + string Namespace { get; } } /// diff --git a/YangParser/SemanticModel/Leaf.cs b/YangParser/SemanticModel/Leaf.cs index 3dd3f20..4a79245 100644 --- a/YangParser/SemanticModel/Leaf.cs +++ b/YangParser/SemanticModel/Leaf.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Text; using YangParser.Parser; +using YangParser.SemanticModel.Builtins; namespace YangParser.SemanticModel; @@ -82,14 +83,39 @@ public string WriteCall { get { - var pre = string.IsNullOrWhiteSpace(Prefix) ? "null" : $"\"{Prefix}\""; - if (Type.Argument is "enumeration" or "bits") + if (Type.GetBaseType(out var prefix) is "enumeration" or "bits") { + if (string.IsNullOrEmpty(prefix)) + { + if (BuiltinTypeReference.IsBuiltin(Type, out _, out _)) //Is direct subtype + { + return $$""" + if({{TargetName}} != default) + { + await writer.WriteStartElementAsync({{xmlPrefix}},"{{Argument}}",{{xmlNs}}); + await writer.WriteStringAsync(GetEncodedValue({{TargetName}}!)); + await writer.WriteEndElementAsync(); + } + """; + } + + //Is local reference. + return $$""" + if({{TargetName}} != default) + { + await writer.WriteStartElementAsync({{xmlPrefix}},"{{Argument}}",{{xmlNs}}); + await writer.WriteStringAsync(YangNode.GetEncodedValue({{TargetName}}!)); + await writer.WriteEndElementAsync(); + } + """; + } + //Is imported reference + var p = prefix.Contains('.') ? prefix : prefix + ":"; return $$""" if({{TargetName}} != default) { - await writer.WriteStartElementAsync({{pre}},"{{Argument}}",null); - await writer.WriteStringAsync(GetEncodedValue({{TargetName}}!)); + await writer.WriteStartElementAsync({{xmlPrefix}},"{{Argument}}",{{xmlNs}}); + await writer.WriteStringAsync({{p}}GetEncodedValue({{TargetName}}!)); await writer.WriteEndElementAsync(); } """; @@ -98,7 +124,7 @@ public string WriteCall return $$""" if({{TargetName}} != default) { - await writer.WriteStartElementAsync({{pre}},"{{Argument}}",null); + await writer.WriteStartElementAsync({{xmlPrefix}},"{{Argument}}",{{xmlNs}}); await writer.WriteStringAsync({{TargetName}}!.ToString()); await writer.WriteEndElementAsync(); } diff --git a/YangParser/SemanticModel/LeafList.cs b/YangParser/SemanticModel/LeafList.cs index 6805cd4..0b3b990 100644 --- a/YangParser/SemanticModel/LeafList.cs +++ b/YangParser/SemanticModel/LeafList.cs @@ -74,13 +74,12 @@ public string WriteCall { get { - var pre = string.IsNullOrWhiteSpace(Prefix) ? "null" : $"\"{Prefix}\""; return $$""" if({{TargetName}} != null) { foreach(var element in {{TargetName}}) { - await writer.WriteStartElementAsync({{pre}},"{{Argument}}",null); + await writer.WriteStartElementAsync({{xmlPrefix}},"{{Argument}}",{{xmlNs}}); await writer.WriteStringAsync(element!.ToString()); await writer.WriteEndElementAsync(); } diff --git a/YangParser/SemanticModel/List.cs b/YangParser/SemanticModel/List.cs index edcb634..77b8fdc 100644 --- a/YangParser/SemanticModel/List.cs +++ b/YangParser/SemanticModel/List.cs @@ -72,7 +72,6 @@ public string WriteCall { get { - var pre = string.IsNullOrWhiteSpace(Prefix) ? "null" : $"\"{Prefix}\""; return $$""" if({{TargetName}} != null) { diff --git a/YangParser/SemanticModel/Module.cs b/YangParser/SemanticModel/Module.cs index f493f25..b24a740 100644 --- a/YangParser/SemanticModel/Module.cs +++ b/YangParser/SemanticModel/Module.cs @@ -13,13 +13,13 @@ public Module(YangStatement statement) : base(statement) throw new SemanticError($"Non-matching Keyword '{statement.Keyword}', expected {Keyword}", statement); var localPrefix = this.GetChild().Argument; var localNS = MakeNamespace(Argument) + ".YangNode."; - XmlNamespace = (Children.First(child => child is Namespace).Argument, localPrefix); + XmlNamespace = (Children.First(child => child is Namespace).Argument, string.Empty); Usings = new() { [localPrefix] = localNS }; ImportedModules[localPrefix] = Argument; - Namespace = localNS; + MyNamespace = localNS; foreach (var child in this.Unwrap()) { @@ -72,7 +72,7 @@ public Module(YangStatement statement) : base(statement) public Dictionary ImportedModules { get; } = []; public Dictionary Usings { get; } - public string Namespace { get; private set; } + public string MyNamespace { get; private set; } public override ChildRule[] PermittedChildren { get; } = [ diff --git a/YangParser/SemanticModel/Notification.cs b/YangParser/SemanticModel/Notification.cs index 211d623..c0a9042 100644 --- a/YangParser/SemanticModel/Notification.cs +++ b/YangParser/SemanticModel/Notification.cs @@ -54,10 +54,9 @@ public async Task ToXML() await writer.WriteStartElementAsync(null,"eventTime",null); await writer.WriteStringAsync(DateTime.UtcNow.ToString("yyyy-MM-ddThh:mm:ssZ")); await writer.WriteEndElementAsync(); - await writer.WriteStartElementAsync("{{XmlNamespace?.Prefix}}","{{Argument}}","{{XmlNamespace?.Namespace}}"); {{xmlWrite}} await writer.WriteEndElementAsync(); - await writer.WriteEndElementAsync(); + await writer.FlushAsync(); return stringBuilder.ToString(); } {{Indent(XmlFunction())}} diff --git a/YangParser/SemanticModel/Rpc.cs b/YangParser/SemanticModel/Rpc.cs index 2e02069..e147cae 100644 --- a/YangParser/SemanticModel/Rpc.cs +++ b/YangParser/SemanticModel/Rpc.cs @@ -77,6 +77,7 @@ public override string ToCode() builder.AppendLine($$""" await writer.WriteEndElementAsync(); await writer.WriteEndElementAsync(); + await writer.FlushAsync(); var xml = stringBuilder.ToString(); """); builder.AppendLine(returnType != "Task" diff --git a/YangParser/SemanticModel/Statement.cs b/YangParser/SemanticModel/Statement.cs index 5811207..1617343 100644 --- a/YangParser/SemanticModel/Statement.cs +++ b/YangParser/SemanticModel/Statement.cs @@ -11,11 +11,14 @@ namespace YangParser.SemanticModel; public abstract class Statement : IStatement { + protected string xmlPrefix => (string.IsNullOrWhiteSpace(Prefix) || string.IsNullOrWhiteSpace(Namespace)) + ? "null" + : $"\"{Prefix}\""; + + protected string xmlNs => string.IsNullOrWhiteSpace(Namespace) ? "null" : $"\"{Namespace}\""; + protected string XmlFunction() { - var ns = XmlNamespace?.Namespace; - ns = ns is null ? "null" : $"\"{ns}\""; - var pre = string.IsNullOrWhiteSpace(Prefix) ? "null" : $"\"{Prefix}\""; var writeCalls = Children.OfType() .Select(t => $"if({t.TargetName} is not null) await {t.TargetName}.WriteXML(writer);"); var elementCalls = Children.OfType() @@ -23,7 +26,7 @@ protected string XmlFunction() return $$""" public async Task WriteXML(XmlWriter writer) { - await writer.WriteStartElementAsync({{pre}},"{{Source.Argument}}",{{ns}}); + await writer.WriteStartElementAsync({{xmlPrefix}},"{{Source.Argument}}",{{xmlNs}}); {{Indent(string.Join("\n", elementCalls))}} {{Indent(string.Join("\n", writeCalls))}} await writer.WriteEndElementAsync(); @@ -33,9 +36,6 @@ public async Task WriteXML(XmlWriter writer) protected string XmlFunctionWithInvisibleSelf() { - var ns = XmlNamespace?.Namespace; - ns = ns is null ? "null" : $"\"{ns}\""; - var pre = string.IsNullOrWhiteSpace(Prefix) ? "null" : $"\"{Prefix}\""; var writeCalls = Children.OfType() .Select(t => $"if({t.TargetName} is not null) await {t.TargetName}.WriteXML(writer);").ToArray(); var elementCalls = Children.OfType() @@ -74,6 +74,7 @@ protected Statement(YangStatement statement, bool validate = true) public (string Namespace, string Prefix)? XmlNamespace { get; set; } public string Prefix => XmlNamespace?.Prefix ?? Parent?.Prefix ?? string.Empty; + public string Namespace => XmlNamespace?.Namespace ?? Parent?.Namespace ?? string.Empty; protected string XmlObjectName => (string.IsNullOrEmpty(Prefix) ? string.Empty : Prefix + ":") + Source.Argument; public string XPath => ((Parent?.XPath ?? String.Empty) + "/").Replace("//", "/") + @@ -184,7 +185,7 @@ public string AttributeString { get { - Attributes.Add("XPath(@\"" + XPath + '"' + ')'); + if (this is IXMLValue || this is IXMLSource) Attributes.Add("XPath(@\"" + XPath + '"' + ')'); return "\n" + string.Join("\n", Attributes.OrderBy(x => x.Length).Select(attr => $"[{attr}]")); } } diff --git a/YangParser/SemanticModel/StatementExtensions.cs b/YangParser/SemanticModel/StatementExtensions.cs index b7112c8..0759eec 100644 --- a/YangParser/SemanticModel/StatementExtensions.cs +++ b/YangParser/SemanticModel/StatementExtensions.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Text; using YangParser.Generator; +using YangParser.SemanticModel.Builtins; namespace YangParser.SemanticModel; @@ -151,7 +152,7 @@ private static Grouping GetGrouping(this Uses use) } else { - var source = use.Root().Children.OfType().FirstOrDefault(m => m.Namespace == prefix) ?? + var source = use.Root().Children.OfType().FirstOrDefault(m => m.MyNamespace == prefix) ?? throw new SemanticError($"Could not find a module with the key {prefix}", use.Source); var grouping = use.FindGrouping(source) ?? throw new SemanticError( $"Could not find a grouping statement to use for 'uses {use.Argument}' in module '{source.Argument}' from prefix '{prefix}'", @@ -257,7 +258,7 @@ public static string Prefix(this string argument, out string name) } else { - var module = source.Root().Children.OfType().FirstOrDefault(m => m.Namespace == prefix); + var module = source.Root().Children.OfType().FirstOrDefault(m => m.MyNamespace == prefix); if (module is null) { Log.Write( @@ -318,4 +319,28 @@ public static string Prefix(this string argument, out string name) if (result is not null) return result; return source.Parent.SearchUpwards(argument); } + + public static string GetBaseType(this Type type, out string prefix) + { + prefix = string.Empty; + while (true) + { + if (BuiltinTypeReference.IsBuiltin(type, out _, out _)) + { + _ = type.Argument.Prefix(out var arg); + return arg; + } + + var newPrefix = type.Argument.Prefix(out _); + if (!string.IsNullOrEmpty(newPrefix)) prefix = newPrefix; + + var source = type.FindReference(type.Argument); + if (source is null) + { + throw new SemanticError($"Could not find source for type definition {type.Argument}", type.Source); + } + + type = source.GetChild(); + } + } } \ No newline at end of file diff --git a/YangSourceTests/BfdIpMhTests.cs b/YangSourceTests/BfdIpMhTests.cs new file mode 100644 index 0000000..9609a9b --- /dev/null +++ b/YangSourceTests/BfdIpMhTests.cs @@ -0,0 +1,20 @@ +using Ietf.Inet.Types; +using Xunit.Abstractions; + +namespace YangSourceTests; + +public class BfdIpMhTests(ITestOutputHelper output) +{ + [Fact] + public async Task NotificationSerializationTest() + { + var notification = new Ietf.Bfd.Ip.Mh.YangNode.MultihopNotification + { + DestAddr = new YangNode.IpAddress(new YangNode.Ipv4Address("192.168.0.1")), + NewState = Ietf.Bfd.Types.YangNode.State.AdminDown + }; + var result = await notification.ToXML(); + output.WriteLine(result); + + } +} \ No newline at end of file diff --git a/YangSourceTests/YangSourceTests.csproj b/YangSourceTests/YangSourceTests.csproj new file mode 100644 index 0000000..7e580b1 --- /dev/null +++ b/YangSourceTests/YangSourceTests.csproj @@ -0,0 +1,27 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + diff --git a/benchmarks/Program.cs b/benchmarks/Program.cs index c605251..9118b8b 100644 --- a/benchmarks/Program.cs +++ b/benchmarks/Program.cs @@ -1,5 +1,6 @@ using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; +using Ietf.Inet.Types; using YangParser; using YangParser.Parser; using YangParser.SemanticModel; @@ -13,6 +14,7 @@ public class ParsingBenchmarks private string source; private YangStatement statement; private IStatement model; + private Ietf.Bfd.Ip.Mh.YangNode.MultihopNotification notification; [GlobalSetup] public void Setup() @@ -20,6 +22,12 @@ public void Setup() source = File.ReadAllText("../../../../lin.yang"); statement = Parser.Parse("lin.yang", source); model = StatementFactory.Create(statement); + notification = new Ietf.Bfd.Ip.Mh.YangNode.MultihopNotification + { + DestAddr = new YangNode.IpAddress(new YangNode.Ipv4Address("1.2.3.4")), + NewState = Ietf.Bfd.Types.YangNode.State.Init, + SourceAddr = new YangNode.IpAddress(new YangNode.Ipv4Address("2.3.4.5")) + }; } [Benchmark] @@ -30,6 +38,18 @@ public void Setup() [Benchmark] public string ToCode() => model.ToCode(); + + [Benchmark] + public Ietf.Bfd.Ip.Mh.YangNode.MultihopNotification MultihopNotificationCreation() => + new Ietf.Bfd.Ip.Mh.YangNode.MultihopNotification + { + DestAddr = new YangNode.IpAddress(new YangNode.Ipv4Address("1.2.3.4")), + NewState = Ietf.Bfd.Types.YangNode.State.Init, + SourceAddr = new YangNode.IpAddress(new YangNode.Ipv4Address("2.3.4.5")) + }; + + [Benchmark] + public async Task SerializerMultihopNotification() => await notification.ToXML(); } internal static class Program diff --git a/benchmarks/benchmarks.csproj b/benchmarks/benchmarks.csproj index 7e6c672..dbd2d79 100644 --- a/benchmarks/benchmarks.csproj +++ b/benchmarks/benchmarks.csproj @@ -12,6 +12,7 @@ + diff --git a/yang-compiler.sln b/yang-compiler.sln index c4e5092..451a981 100644 --- a/yang-compiler.sln +++ b/yang-compiler.sln @@ -16,6 +16,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestData", "TestData", "{E9 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YangSource", "TestData\YangSource\YangSource.csproj", "{714DB9A0-7B83-4CBA-807B-9413C8FB4139}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YangSourceTests", "YangSourceTests\YangSourceTests.csproj", "{D7772430-87F7-4707-8BC1-4F5C601C4AD3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -38,6 +40,10 @@ Global {714DB9A0-7B83-4CBA-807B-9413C8FB4139}.Debug|Any CPU.Build.0 = Debug|Any CPU {714DB9A0-7B83-4CBA-807B-9413C8FB4139}.Release|Any CPU.ActiveCfg = Release|Any CPU {714DB9A0-7B83-4CBA-807B-9413C8FB4139}.Release|Any CPU.Build.0 = Release|Any CPU + {D7772430-87F7-4707-8BC1-4F5C601C4AD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D7772430-87F7-4707-8BC1-4F5C601C4AD3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D7772430-87F7-4707-8BC1-4F5C601C4AD3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D7772430-87F7-4707-8BC1-4F5C601C4AD3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {E35D4026-B610-4BDF-9DFE-497FDD49EACD} = {7DF11EFE-894D-4357-B7CE-A390651300BC} @@ -45,5 +51,6 @@ Global {9F3CA1F2-9BBE-4D01-88B3-6218EB59DC3C} = {53A82AB5-2141-453F-B2E5-2E93E2D3B671} {E9FC49A1-6D19-4B52-B1AE-9DF0AC6B0663} = {7DF11EFE-894D-4357-B7CE-A390651300BC} {714DB9A0-7B83-4CBA-807B-9413C8FB4139} = {E9FC49A1-6D19-4B52-B1AE-9DF0AC6B0663} + {D7772430-87F7-4707-8BC1-4F5C601C4AD3} = {E9FC49A1-6D19-4B52-B1AE-9DF0AC6B0663} EndGlobalSection EndGlobal diff --git a/yang-compiler.sln.DotSettings.user b/yang-compiler.sln.DotSettings.user index 6af721b..f15c22a 100644 --- a/yang-compiler.sln.DotSettings.user +++ b/yang-compiler.sln.DotSettings.user @@ -1,4 +1,9 @@  <SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from &lt;Tests&gt;\&lt;Compiler.Tests&gt;" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> - <Project Location="C:\Projects\yang\yang-compiler\Compiler.Tests" Presentation="&lt;Tests&gt;\&lt;Compiler.Tests&gt;" /> + <Or> + <Project Location="C:\Projects\yang\yang-compiler\Compiler.Tests" Presentation="&lt;Tests&gt;\&lt;Compiler.Tests&gt;" /> + <TestAncestor> + <TestId>xUnit::D7772430-87F7-4707-8BC1-4F5C601C4AD3::net8.0::YangSourceTests.BfdIpMhTests.NotificationSerializationTest</TestId> + </TestAncestor> + </Or> </SessionState> \ No newline at end of file