From 6bdae8ac91bebb93aefa889829d59acd10bae8c6 Mon Sep 17 00:00:00 2001 From: caran Date: Tue, 18 Jun 2024 15:58:20 +0200 Subject: [PATCH] More proper implementation of 'leafref' --- YangSourceTests/RpcTests.cs | 37 ++--- benchmarks/Program.cs | 6 +- dotnetYang/SemanticModel/Augment.cs | 16 +- .../Builtins/BuiltinTypeReference.cs | 28 +--- .../SemanticModel/Builtins/LeafReference.cs | 56 +++++-- dotnetYang/SemanticModel/CompilationUnit.cs | 2 +- dotnetYang/SemanticModel/DefaultValue.cs | 6 +- dotnetYang/SemanticModel/Grouping.cs | 24 ++- dotnetYang/SemanticModel/Statement.cs | 2 +- .../SemanticModel/StatementExtensions.cs | 148 +++++++++++++++++- dotnetYang/SemanticModel/Type.cs | 1 - 11 files changed, 255 insertions(+), 71 deletions(-) diff --git a/YangSourceTests/RpcTests.cs b/YangSourceTests/RpcTests.cs index c224382..b1396c9 100644 --- a/YangSourceTests/RpcTests.cs +++ b/YangSourceTests/RpcTests.cs @@ -11,9 +11,9 @@ public class RpcTests(ITestOutputHelper outputHelper) { private class TestChannel : IChannel, IAsyncDisposable { - public string? LastXML => Encoding.UTF8.GetString(((MemoryStream)WriteStream).GetBuffer()).Replace("\0", ""); - public string? LastWritten => Encoding.UTF8.GetString(((MemoryStream)ReadStream).GetBuffer()).Replace("\0", ""); - private ExampleYangServer server = new(); + public string LastXML => Encoding.UTF8.GetString(((MemoryStream)WriteStream).GetBuffer()).Replace("\0", ""); + public string LastWritten => Encoding.UTF8.GetString(((MemoryStream)ReadStream).GetBuffer()).Replace("\0", ""); + private readonly ExampleYangServer server = new(); public async Task Send() { @@ -24,8 +24,8 @@ public async Task Send() (LastReadIndex, WriteStream.Position) = (WriteStream.Position, LastReadIndex); } - private long LastSentIndex = 0; - private long LastReadIndex = 0; + private long LastSentIndex; + private long LastReadIndex; public Stream WriteStream { get; } = new MemoryStream(); public Stream ReadStream { get; } = new MemoryStream(); @@ -49,8 +49,8 @@ public async Task RpcSend() var reply = await Ietf.Connection.Oriented.Oam.YangNode.Traceroute(channel, 1, new Ietf.Connection.Oriented.Oam.YangNode.TracerouteInput { - MaNameStringValue = new Ietf.Connection.Oriented.Oam.YangNode.TracerouteInput.MaNameString(), - MdNameStringValue = new Ietf.Connection.Oriented.Oam.YangNode.TracerouteInput.MdNameString(), + MaNameString = "MA", + MdNameString = "MD", Ttl = 2, Count = 4, Interval = 6, @@ -65,7 +65,6 @@ public async Task RpcSend() } } }, - SourceMepValue = new(), CommandSubType = Ietf.Connection.Oriented.Oam.YangNode.CommandSubTypeIdentity.Proactive }); outputHelper.WriteLine(channel.LastXML); @@ -81,11 +80,11 @@ public async Task RpcSend() var replyString = Encoding.UTF8.GetString(ms.GetBuffer()); Assert.Equal(channel.LastWritten, replyString); - reply = await Ietf.Connection.Oriented.Oam.YangNode.Traceroute(channel, 2, + await Ietf.Connection.Oriented.Oam.YangNode.Traceroute(channel, 2, new Ietf.Connection.Oriented.Oam.YangNode.TracerouteInput { - MaNameStringValue = new Ietf.Connection.Oriented.Oam.YangNode.TracerouteInput.MaNameString(), - MdNameStringValue = new Ietf.Connection.Oriented.Oam.YangNode.TracerouteInput.MdNameString(), + MaNameString = "Ma", + MdNameString = "Md", Ttl = 2, Count = 4, Interval = 6, @@ -100,17 +99,9 @@ public async Task RpcSend() } } }, - SourceMepValue = new(), + SourceMep = "Meppy", CommandSubType = Ietf.Connection.Oriented.Oam.YangNode.CommandSubTypeIdentity.Proactive }); - // Assert.Equal( - // reply.Response![0].Mip!.MipAddress!.IpAddressCaseValue!.IpAddress!.Ipv4AddressValue! - // .WrittenValue, - // output.Response![0].Mip!.MipAddress!.IpAddressCaseValue!.IpAddress!.Ipv4AddressValue! - // .WrittenValue); - // Assert.Equal( - // reply.Response![1].Ttl, - // output.Response![1].Ttl); } private static readonly Ietf.Alarms.YangNode.AlarmsContainer root = new() @@ -221,7 +212,7 @@ public async Task ExceptionThrowingTest() await using var channel = new TestChannel(); try { - var result = await Ietf.Subscribed.Notifications.YangNode.EstablishSubscription(channel, + await Ietf.Subscribed.Notifications.YangNode.EstablishSubscription(channel, Random.Shared.Next(), new Ietf.Subscribed.Notifications.YangNode.EstablishSubscriptionInput { @@ -240,9 +231,7 @@ public async Task ExceptionThrowingTest() EstablishSubscriptionInput.TargetChoice.StreamCaseValueCase. StreamFilterChoice.ByReferenceCaseValueCase() { - StreamFilterName = - new Ietf.Subscribed.Notifications.YangNode. - StreamFilterRef() + StreamFilterName = "FilteryMacFilter" } } } diff --git a/benchmarks/Program.cs b/benchmarks/Program.cs index 202f205..9801dd8 100644 --- a/benchmarks/Program.cs +++ b/benchmarks/Program.cs @@ -25,8 +25,8 @@ public class ParsingBenchmarks private static readonly Ietf.Connection.Oriented.Oam.YangNode.TracerouteInput input = new() { - MaNameStringValue = new Ietf.Connection.Oriented.Oam.YangNode.TracerouteInput.MaNameString(), - MdNameStringValue = new Ietf.Connection.Oriented.Oam.YangNode.TracerouteInput.MdNameString(), + MaNameString = "Mama String", + MdNameString = "Maddy String", Ttl = 2, Count = 4, Interval = 6, @@ -44,7 +44,7 @@ public class ParsingBenchmarks } } }, - SourceMepValue = new(), + SourceMep = "Meppity", CommandSubType = Ietf.Connection.Oriented.Oam.YangNode.CommandSubTypeIdentity.Proactive }; diff --git a/dotnetYang/SemanticModel/Augment.cs b/dotnetYang/SemanticModel/Augment.cs index a0d091b..85de58d 100644 --- a/dotnetYang/SemanticModel/Augment.cs +++ b/dotnetYang/SemanticModel/Augment.cs @@ -1,4 +1,5 @@ using System.Linq; +using YangParser.Generator; using YangParser.Parser; using YangParser.SemanticModel.Builtins; @@ -186,8 +187,21 @@ private void PrepareTransitionTo(IStatement top) continue; } + if(type.Argument.Contains("leafref")){ + var path = type.GetChild(); + var value = path.Argument; + var components = value.Split('/'); + var index = value.StartsWith("/") ? 1 : 0; + var prefix = components[index].Prefix(out var component); + if (string.IsNullOrWhiteSpace(prefix)) + { + components[index] = this.GetInheritedPrefix() + ":" + components[index]; + } + + path.Argument = string.Join("/", components); + } - if (BuiltinTypeReference.IsBuiltin(type, out _, out _)) + if (BuiltinTypeReference.IsBuiltinKeyword(type.Argument)) { continue; } diff --git a/dotnetYang/SemanticModel/Builtins/BuiltinTypeReference.cs b/dotnetYang/SemanticModel/Builtins/BuiltinTypeReference.cs index 9eea476..aafac37 100644 --- a/dotnetYang/SemanticModel/Builtins/BuiltinTypeReference.cs +++ b/dotnetYang/SemanticModel/Builtins/BuiltinTypeReference.cs @@ -47,7 +47,8 @@ public static bool IsBuiltin(Type type, out string? cSharpType, out string? defi public static bool IsBuiltinKeyword(string keyword) { - return m_builtIns.Any(b => b.Name == keyword); + keyword.Prefix(out var name); + return m_builtIns.Any(b => b.Name == name); } public static string Stringification(Type type, string targetName) @@ -85,14 +86,6 @@ public static string DefaultPattern(IStatement statement, IEnumerable st } """; break; - case "leafref": - parseFunction = $$""" - public static {{typeName}} Parse(string value) - { - return new {{typeName}}(new {{baseTypeName}}()); - } - """; - break; case "bits": case "enumeration": case "identityref": @@ -198,15 +191,6 @@ public static string ValueTransformation(Type type, string typeName, string targ """; } - if (type.Argument == "leafref") - { - return $""" - {GetText(argument)} - {target} = new {typeName}(); - {EndElement(argument)} - """; - } - var baseType = type.GetBaseType(out var prefix, out var chosenType); switch (baseType) { @@ -231,7 +215,7 @@ public static string ValueTransformation(Type type, string typeName, string targ if (string.IsNullOrEmpty(prefix)) { //Is local reference. - return IsBuiltin(type, out _, out _) + return IsBuiltinKeyword(type.Argument) ? //Is direct subtype $""" {GetText(argument)} @@ -274,7 +258,7 @@ public static string ValueParsing(Type type, string typeName) { if (typeName == "string") { - return $"return value;"; + return "return value;"; } var baseType = type.GetBaseType(out var prefix, out var chosen); @@ -284,8 +268,6 @@ public static string ValueParsing(Type type, string typeName) return $"return {typeName}.Parse(value);"; case "empty": return "if(string.IsNullOrWhiteSpace(value)) return new object();"; - case "leafref": - return $"if(string.IsNullOrWhiteSpace(value)) return new {typeName}();"; case "bits": case "enumeration": case "identityref": @@ -295,7 +277,7 @@ public static string ValueParsing(Type type, string typeName) if (string.IsNullOrEmpty(prefix)) { //Is local reference. - return IsBuiltin(type, out _, out _) + return IsBuiltinKeyword(type.Argument) ? //Is direct subtype $"return Get{local}Value(value);" : $"return YangNode.Get{local}Value(value);"; diff --git a/dotnetYang/SemanticModel/Builtins/LeafReference.cs b/dotnetYang/SemanticModel/Builtins/LeafReference.cs index 785f9d8..c39c00a 100644 --- a/dotnetYang/SemanticModel/Builtins/LeafReference.cs +++ b/dotnetYang/SemanticModel/Builtins/LeafReference.cs @@ -1,18 +1,54 @@ +using System; using System.Linq; +using System.Text.RegularExpressions; namespace YangParser.SemanticModel.Builtins; public class LeafReference() : BuiltinType("leafref", (statement) => { var path = (Path)statement.Children.First(c => c is Path); - var name = BuiltinTypeReference.TypeName(statement); - var definition = $$""" - {{statement.DescriptionString}}{{statement.AttributeString}} - public class {{name}}() : InstanceIdentifier("{{path.Argument}}") - { - public new static {{name}} Parse(string value) => new(); - } - - """; - return (name, definition); + var target = statement.Parent.Navigate(path.Argument); + var prefix = path.Argument.Split('/').Last().Prefix(out _); + var assignation = prefix.Contains('.') ? prefix : prefix + ":"; + if (statement.FindSourceFor(prefix) == target.GetModule()) + { + prefix = string.Empty; + assignation = string.Empty; + } + + var type = target!.GetChild(); + var bname = target switch + { + IXMLReadValue rw => rw.ClassName, + IXMLParseable ps => ps.ClassName, + _ => "string" + }; + if (bname.Contains(":") || bname.Contains(".")) + { + var p = bname.Prefix(out _); + if (bname.Contains(":") && !statement.GetModule()!.Usings.ContainsKey(p)) + { + statement.GetModule()!.Usings[p] = target.GetModule()!.Usings[p]; + } + + return (bname, null); + } + + if (bname == "string") return (bname, null); + if (type.Definition is null && !BuiltinTypeReference.IsBuiltinKeyword(type.Argument)) + { + return (target.ModuleQualifiedClassName(), null); + } + + if (string.IsNullOrWhiteSpace(prefix) && !BuiltinTypeReference.IsBuiltinKeyword(type.Argument)) + { + return (target.FullyQualifiedClassName(), null); + } + + if (BuiltinTypeReference.IsBuiltin(type, out var name, out var def)) + { + return def is null ? (name!, null) : (target.FullyQualifiedClassName(), null); + } + + return (assignation + bname, null); }); \ No newline at end of file diff --git a/dotnetYang/SemanticModel/CompilationUnit.cs b/dotnetYang/SemanticModel/CompilationUnit.cs index cf6af58..39549f2 100644 --- a/dotnetYang/SemanticModel/CompilationUnit.cs +++ b/dotnetYang/SemanticModel/CompilationUnit.cs @@ -141,7 +141,7 @@ public static async Task Receive(this IYangServer server, global::System.IO.Stre await writer.FlushAsync(); output.Position = initialPosition; output.SetLength(initialLength); - output.SerializeRegularExceptionAsync(ex,id); + await output.SerializeRegularExceptionAsync(ex,id); } } public static async Task ReceiveRPC(this IYangServer server, XmlReader reader, XmlWriter writer) diff --git a/dotnetYang/SemanticModel/DefaultValue.cs b/dotnetYang/SemanticModel/DefaultValue.cs index b84f173..a082a43 100644 --- a/dotnetYang/SemanticModel/DefaultValue.cs +++ b/dotnetYang/SemanticModel/DefaultValue.cs @@ -83,11 +83,11 @@ private string GetTypeSpecification(string prefix, string value, Type type, bool return $"new(\"{Argument}\")"; } - if (BuiltinTypeReference.IsBuiltin(subType, out var typeName, out _)) + if (BuiltinTypeReference.IsBuiltinKeyword(subType.Argument)) { return chained - ? "new(" + possible + $"/*{typeName}*/" + ")" - : possible + $"/*{typeName}*/"; + ? "new(" + possible + ")" + : possible; } return "new(new " + MakeName(subType.Argument) + "(" + possible + "))"; diff --git a/dotnetYang/SemanticModel/Grouping.cs b/dotnetYang/SemanticModel/Grouping.cs index bd38d58..6fea19c 100644 --- a/dotnetYang/SemanticModel/Grouping.cs +++ b/dotnetYang/SemanticModel/Grouping.cs @@ -53,10 +53,12 @@ public IStatement[] WithUse(Uses use) { use.GetModule()?.Rpcs.Add(rpc); } + if (child is Notification notification) { use.GetModule()?.Notifications.Add(notification); } + if (child is Action action) { use.GetModule()?.Actions.Add(action); @@ -79,7 +81,23 @@ public IStatement[] WithUse(Uses use) continue; } - if (BuiltinTypeReference.IsBuiltin(type, out _, out _)) + if (type.Argument.Contains("leafref")) + { + var path = type.GetChild(); + var value = path.Argument; + var components = value.Split('/'); + var index = value.StartsWith("/") ? 1 : 0; + var prefix = components[index].Prefix(out var component); + if (string.IsNullOrWhiteSpace(prefix)) + { + components[index] = this.GetInheritedPrefix() + ":" + components[index]; + } + + path.Argument = string.Join("/", components); + } + + + if (BuiltinTypeReference.IsBuiltinKeyword(type.Argument)) { continue; } @@ -137,11 +155,11 @@ public IStatement[] WithUse(Uses use) { element.Prefix(out var name); var origin = current; - current = origin.Children.FirstOrDefault(c => c.Argument == name); + current = StatementExtensions.LocateChildWithInvisibleAllowed(current.Children, name); if (current is null) { Log.Write( - $"Could not find part '{name}' of path {refinement.Argument} in source {origin.Source.Keyword} {origin.Argument}"); + $"Missing '{name}' in '{refinement.Argument}' at '{origin.Source.Keyword} {origin.Argument}' [{string.Join(", ", origin.Children.Select(o => o.GetType().Name + " " + o.Argument))}]"); break; //Target not present, nothing to refine. } } diff --git a/dotnetYang/SemanticModel/Statement.cs b/dotnetYang/SemanticModel/Statement.cs index f913326..747cf5e 100644 --- a/dotnetYang/SemanticModel/Statement.cs +++ b/dotnetYang/SemanticModel/Statement.cs @@ -385,7 +385,7 @@ public static string Capitalize(string section) return (char.ToUpperInvariant(first) + rest); } - protected static string MakeNamespace(string argument) + public static string MakeNamespace(string argument) { var output = new StringBuilder(argument.Length); foreach (var section in argument.Split('-')) diff --git a/dotnetYang/SemanticModel/StatementExtensions.cs b/dotnetYang/SemanticModel/StatementExtensions.cs index 5baa8a3..a7d4229 100644 --- a/dotnetYang/SemanticModel/StatementExtensions.cs +++ b/dotnetYang/SemanticModel/StatementExtensions.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using YangParser.Generator; using YangParser.SemanticModel.Builtins; @@ -8,6 +9,143 @@ namespace YangParser.SemanticModel; public static class StatementExtensions { + public static string FullyQualifiedClassName(this IStatement? statement) + { + List classChain = new(); + while (statement is not Module && statement is not null) + { + switch (statement) + { + case IXMLParseable xml: + xml.ClassName.Prefix(out var name); + classChain.Insert(0, name); + break; + case IXMLReadValue readValue: + readValue.ClassName.Prefix(out var rname); + classChain.Insert(0, rname); + break; + } + + statement = statement.Parent; + } + + if (statement is Module module) + { + classChain.Insert(0, "YangNode"); + classChain.Insert(0, Statement.MakeNamespace(module.Argument)); + } + + return string.Join(".", classChain); + } + + public static string ModuleQualifiedClassName(this IStatement? statement) + { + List classChain = []; + if (statement is IXMLReadValue rw) classChain.Add(rw.ClassName); + else if (statement is IXMLParseable parseable) classChain.Add(parseable.ClassName); + while (statement is not Module && statement is not null) + { + statement = statement.Parent; + } + + if (statement is Module module) + { + classChain.Insert(0, "YangNode"); + classChain.Insert(0, Statement.MakeNamespace(module.Argument)); + } + + return string.Join(".", classChain); + } + + public static IStatement? Navigate(this IStatement? context, string xpath) + { + xpath = Regex.Replace(xpath, @"\[.*?]", string.Empty); + if (xpath.StartsWith("/")) context = context.GetModule(); + var components = xpath.Split('/'); + foreach (var path in components) + { + var start = context; + var prefix = path.Prefix(out var truePath); + if (context is Module && !string.IsNullOrWhiteSpace(prefix)) + { + context = context.FindSourceFor(prefix); + if (context is null) + { + Log.Write($"No module found for '{prefix}' at '{truePath}' in {xpath}, bailing"); + return context; + } + } + + if (string.IsNullOrWhiteSpace(path)) continue; + if (path == "..") + { + context = context?.Parent; + while (context is Case or Choice or Input or Output) context = context.Parent; + if (context is null) + { + Log.Write($"Parent null at '{path}' in {xpath}, bailing"); + return context; + } + + continue; + } + + var children = context!.Children; + context = LocateChild(children, truePath); + if (context is null) + { + Log.Write( + $"Null at '{truePath}' in {xpath} in '{start?.GetType().Name} {start?.Argument}', children: [{string.Join(", ", children.Where(c => c is IXMLParseable or IXMLReadValue).Select(c => $"{c.GetType().Name} \"{c.Argument}\""))}]"); + return context; + } + } + + return context; + } + + public static IStatement? LocateChild(IEnumerable children, string truePath) + { + foreach (var child in children) + { + switch (child) + { + case Choice: + case Case: + case Output: + case Input: + var potential = LocateChild(child.Children, truePath); + if (potential is not null) return potential; + break; + case IXMLParseable or IXMLReadValue when child.Argument == truePath: + return child; + } + } + + return null; + } + + public static IStatement? LocateChildWithInvisibleAllowed(IEnumerable children, string truePath) + { + foreach (var child in children) + { + switch (child) + { + case Choice: + case Case: + case Output: + case Input: + if (child.Argument == truePath) return child; + var potential = LocateChild(child.Children, truePath); + if (potential is not null) return potential; + break; + case IXMLParseable or IXMLReadValue when child.Argument == truePath: + return child; + } + } + + return null; + } + private static IStatement Root(this IStatement statement) { while (statement.Parent is not null) @@ -81,7 +219,7 @@ public static IEnumerable Unwrap(this IStatement source) public static ITopLevelStatement? FindSourceFor(this IStatement source, string prefix) { - var module = source.GetModule(); + var module = source as Module ?? source.GetModule(); if (module?.ImportedModules.TryGetValue(prefix, out var moduleName) == true) { return module.Root().Children.OfType().FirstOrDefault(s => s.Argument == moduleName); @@ -327,6 +465,14 @@ public static string GetBaseType(this Type type, out string prefix, out Type cho { if (BuiltinTypeReference.IsBuiltinKeyword(type.Argument)) { + if (type.Argument == "leafref") + { + var path = (Path)type.Children.First(c => c is Path); + var target = type.Parent.Navigate(path.Argument); + type = target!.GetChild(); + continue; + } + chosenType = type; _ = type.Argument.Prefix(out var arg); return arg; diff --git a/dotnetYang/SemanticModel/Type.cs b/dotnetYang/SemanticModel/Type.cs index af58cec..06553bb 100644 --- a/dotnetYang/SemanticModel/Type.cs +++ b/dotnetYang/SemanticModel/Type.cs @@ -35,7 +35,6 @@ public override string ToCode() { return string.Empty; } - public string? Definition { get