diff --git a/Capnp.Net.sln b/Capnp.Net.sln index e12ef97..41f5968 100644 --- a/Capnp.Net.sln +++ b/Capnp.Net.sln @@ -11,6 +11,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Capnp.Net.Runtime.Tests.Std EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Capnp.Net.Runtime.Tests.Core21", "Capnp.Net.Runtime.Tests.Core21\Capnp.Net.Runtime.Tests.Core21.csproj", "{58E8FFC8-D207-4B0F-842A-58ED9D3D9EEF}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "capnpc-csharp.tests", "capnpc-csharp.tests\capnpc-csharp.tests.csproj", "{B77AC567-E232-4072-85C3-8689566BF3D4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -33,6 +35,10 @@ Global {58E8FFC8-D207-4B0F-842A-58ED9D3D9EEF}.Debug|Any CPU.Build.0 = Debug|Any CPU {58E8FFC8-D207-4B0F-842A-58ED9D3D9EEF}.Release|Any CPU.ActiveCfg = Release|Any CPU {58E8FFC8-D207-4B0F-842A-58ED9D3D9EEF}.Release|Any CPU.Build.0 = Release|Any CPU + {B77AC567-E232-4072-85C3-8689566BF3D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B77AC567-E232-4072-85C3-8689566BF3D4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B77AC567-E232-4072-85C3-8689566BF3D4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B77AC567-E232-4072-85C3-8689566BF3D4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/capnpc-csharp.tests/Properties/Resources.Designer.cs b/capnpc-csharp.tests/Properties/Resources.Designer.cs new file mode 100644 index 0000000..7c7c2e9 --- /dev/null +++ b/capnpc-csharp.tests/Properties/Resources.Designer.cs @@ -0,0 +1,123 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace capnpc_csharp.Tests.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("capnpc_csharp.Tests.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] schema_with_offsets_capnp { + get { + object obj = ResourceManager.GetObject("schema_with_offsets_capnp", resourceCulture); + return ((byte[])(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] UnitTest1_capnp { + get { + object obj = ResourceManager.GetObject("UnitTest1_capnp", resourceCulture); + return ((byte[])(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] UnitTest10_capnp { + get { + object obj = ResourceManager.GetObject("UnitTest10_capnp", resourceCulture); + return ((byte[])(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] UnitTest2_capnp { + get { + object obj = ResourceManager.GetObject("UnitTest2_capnp", resourceCulture); + return ((byte[])(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] UnitTest20_capnp { + get { + object obj = ResourceManager.GetObject("UnitTest20_capnp", resourceCulture); + return ((byte[])(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] UnitTest3_capnp { + get { + object obj = ResourceManager.GetObject("UnitTest3_capnp", resourceCulture); + return ((byte[])(obj)); + } + } + } +} diff --git a/capnpc-csharp.tests/Properties/Resources.resx b/capnpc-csharp.tests/Properties/Resources.resx new file mode 100644 index 0000000..6d2bda3 --- /dev/null +++ b/capnpc-csharp.tests/Properties/Resources.resx @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\Resources\UnitTest10.capnp.bin;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\UnitTest20.capnp.bin;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\UnitTest3.capnp.bin;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\UnitTest2.capnp.bin;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\UnitTest1.capnp.bin;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\schema-with-offsets.capnp.bin;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/capnpc-csharp.tests/Resources/UnitTest1.capnp.bin b/capnpc-csharp.tests/Resources/UnitTest1.capnp.bin new file mode 100644 index 0000000..ae47486 Binary files /dev/null and b/capnpc-csharp.tests/Resources/UnitTest1.capnp.bin differ diff --git a/capnpc-csharp.tests/Resources/UnitTest10.capnp.bin b/capnpc-csharp.tests/Resources/UnitTest10.capnp.bin new file mode 100644 index 0000000..dade3aa Binary files /dev/null and b/capnpc-csharp.tests/Resources/UnitTest10.capnp.bin differ diff --git a/capnpc-csharp.tests/Resources/UnitTest2.capnp.bin b/capnpc-csharp.tests/Resources/UnitTest2.capnp.bin new file mode 100644 index 0000000..5a2eafc Binary files /dev/null and b/capnpc-csharp.tests/Resources/UnitTest2.capnp.bin differ diff --git a/capnpc-csharp.tests/Resources/UnitTest20.capnp.bin b/capnpc-csharp.tests/Resources/UnitTest20.capnp.bin new file mode 100644 index 0000000..48f5e96 Binary files /dev/null and b/capnpc-csharp.tests/Resources/UnitTest20.capnp.bin differ diff --git a/capnpc-csharp.tests/Resources/UnitTest3.capnp.bin b/capnpc-csharp.tests/Resources/UnitTest3.capnp.bin new file mode 100644 index 0000000..c0a1dae Binary files /dev/null and b/capnpc-csharp.tests/Resources/UnitTest3.capnp.bin differ diff --git a/capnpc-csharp.tests/Resources/schema-with-offsets.capnp.bin b/capnpc-csharp.tests/Resources/schema-with-offsets.capnp.bin new file mode 100644 index 0000000..1b61d19 Binary files /dev/null and b/capnpc-csharp.tests/Resources/schema-with-offsets.capnp.bin differ diff --git a/capnpc-csharp.tests/UnitTest1.capnp b/capnpc-csharp.tests/UnitTest1.capnp new file mode 100644 index 0000000..f638014 --- /dev/null +++ b/capnpc-csharp.tests/UnitTest1.capnp @@ -0,0 +1,9 @@ +@0x93d3c4d19cd84a4c; + +enum Enumerant { + byte @0; +} + +struct Foo { + foo @0: UInt8; +} diff --git a/capnpc-csharp.tests/UnitTest10.capnp b/capnpc-csharp.tests/UnitTest10.capnp new file mode 100644 index 0000000..226e94b --- /dev/null +++ b/capnpc-csharp.tests/UnitTest10.capnp @@ -0,0 +1,9 @@ +@0xbbfd48ae4b99d012; + +using Cxx = import "/capnp/c++.capnp"; + +$Cxx.namespace("Foo::Bar::Baz"); + +struct Outer { + inner @0: import "UnitTest10b.capnp".Inner ; +} diff --git a/capnpc-csharp.tests/UnitTest10b.capnp b/capnpc-csharp.tests/UnitTest10b.capnp new file mode 100644 index 0000000..4d49fee --- /dev/null +++ b/capnpc-csharp.tests/UnitTest10b.capnp @@ -0,0 +1,7 @@ +@0xaf95c1c78b01be97; + +using Cxx = import "/capnp/c++.capnp"; + +$Cxx.namespace("Foo::Garf::Snarf"); + +struct Inner {} diff --git a/capnpc-csharp.tests/UnitTest2.capnp b/capnpc-csharp.tests/UnitTest2.capnp new file mode 100644 index 0000000..e4cbcb9 --- /dev/null +++ b/capnpc-csharp.tests/UnitTest2.capnp @@ -0,0 +1,11 @@ +@0xf6041efc5e8b1e59; + +interface Interface1(V1) {} + +interface Interface { + method @0 () -> (arg : AnyPointer); + method1 @1 () -> (arg : Interface1(AnyPointer)); + method2 @2 () -> (arg : Interface2(AnyPointer)); +} + +interface Interface2(V2) {} diff --git a/capnpc-csharp.tests/UnitTest20.capnp b/capnpc-csharp.tests/UnitTest20.capnp new file mode 100644 index 0000000..edf91c9 --- /dev/null +++ b/capnpc-csharp.tests/UnitTest20.capnp @@ -0,0 +1,5 @@ +@0xcebb7a61a86a7492; + +annotation anAnnotation(file): Text; + +const aConstant :UInt16 = 42; diff --git a/capnpc-csharp.tests/UnitTest3.capnp b/capnpc-csharp.tests/UnitTest3.capnp new file mode 100644 index 0000000..14abe12 --- /dev/null +++ b/capnpc-csharp.tests/UnitTest3.capnp @@ -0,0 +1,6 @@ +@0xb7158f7fa52b8db6; + +using Cxx = import "/capnp/c++.capnp"; + +$Cxx.namespace("Foo.Bar.Baz"); + diff --git a/capnpc-csharp.tests/UnitTests.cs b/capnpc-csharp.tests/UnitTests.cs new file mode 100644 index 0000000..c009ddd --- /dev/null +++ b/capnpc-csharp.tests/UnitTests.cs @@ -0,0 +1,129 @@ +using capnpc_csharp.Tests.Properties; +using Capnp; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace CapnpC +{ + [TestClass] + public class UnitTests + { + + [TestMethod] + public void Test00Enumerant() + { + var model = Load(Resources.UnitTest1_capnp); + Assert.AreEqual("@byte", GetTypeDef(0xc8461867c409f5d4, model).Enumerants[0].Literal); + } + + [TestMethod] + public void Test01NestedClash() + { + var model = Load(Resources.UnitTest1_capnp); + var structFoo = GetTypeDef(0x93db6ba5509bac24, model); + var codeGen = NewGeneratorFor(model); + codeGen.Transform(model.FilesToGenerate.First()); + var names = codeGen.GetNames(); + var fieldName = names.GetCodeIdentifier(structFoo.Fields[0]).ToString(); + Assert.AreEqual("Foo", structFoo.Name); + Assert.AreNotEqual(structFoo.Name, fieldName); + } + + [TestMethod] + public void Test02ForwardInheritance() + { + var model = Load(Resources.UnitTest2_capnp); + var codeGen = NewGeneratorFor(model); + codeGen.Transform(model.FilesToGenerate.First()); + // Should not throw + } + + [TestMethod] + public void Test03NonGeneratedNodeSkip() + { + var model = Load(Resources.UnitTest3_capnp); + // Should not throw + } + + [TestMethod] + public void Test10ImportedNamespaces() + { + var model = Load(Resources.UnitTest10_capnp); + var codeGen = NewGeneratorFor(model); + var genFile = model.FilesToGenerate.First(); + var outerTypeDef = genFile.NestedTypes.First(); + var outerType = Model.Types.FromDefinition(outerTypeDef); + var innerType = outerTypeDef.Fields[0].Type; + var innerTypeDef = innerType.Definition; + var names = codeGen.GetNames(); + var outerNameSyntax = names.GetQName(outerType, outerTypeDef); + var innerNameSyntax = names.GetQName(innerType, outerTypeDef); + string[] outerNamespace = { "Foo", "Bar", "Baz" }; + string[] innerNamespace = { "Foo", "Garf", "Snarf" }; + CollectionAssert.AreEqual(outerNamespace, (outerTypeDef.DeclaringElement as Model.GenFile).Namespace); + CollectionAssert.AreEqual(innerNamespace, (innerType.Definition.DeclaringElement as Model.GenFile).Namespace); + string outerNSStr = String.Join('.', outerNamespace); + string innerNSStr = String.Join('.', innerNamespace); + Assert.AreEqual($"{outerNSStr}.Outer", outerNameSyntax.ToString()); + Assert.AreEqual($"{innerNSStr}.Inner", innerNameSyntax.ToString()); + } + + [TestMethod] + public void Test20AnnotationAndConst() + { + var model = Load(Resources.UnitTest20_capnp); + var codeGen = NewGeneratorFor(model); + codeGen.Transform(model.FilesToGenerate.First()); + // Should not throw + } + + [TestMethod] + public void Test30SchemaCapnp() + { + var model = Load(Resources.schema_with_offsets_capnp); + var codeGen = NewGeneratorFor(model); + codeGen.Transform(model.FilesToGenerate.First()); + // Should not throw + } + + static Generator.CodeGenerator NewGeneratorFor(Model.SchemaModel model) + => new Generator.CodeGenerator(model, new Generator.GeneratorOptions()); + + static Model.TypeDefinition GetTypeDef(ulong id, Model.SchemaModel model) + { + foreach (var defs in model.FilesToGenerate.Select(f => f.NestedTypes)) + { + if (GetTypeDef(id, defs) is Model.TypeDefinition def) return def; + } + return null; + } + + static Model.TypeDefinition GetTypeDef(ulong id, IEnumerable defs) + { + foreach (var def in defs) + { + if (def.Id == id) return def; + var sub = GetTypeDef(id, def.NestedTypes); + if (sub != null) return sub; + } + return null; + } + + static Model.SchemaModel Load(byte[] data) + { + WireFrame segments; + var input = new MemoryStream(data); + using (input) + { + segments = Framing.ReadSegments(input); + } + var dec = DeserializerState.CreateRoot(segments); + var reader = Schema.CodeGeneratorRequest.Reader.Create(dec); + var model = Model.SchemaModel.Create(reader); + return model; + } + } +} diff --git a/capnpc-csharp.tests/capnpc-csharp.tests.csproj b/capnpc-csharp.tests/capnpc-csharp.tests.csproj new file mode 100644 index 0000000..3be689f --- /dev/null +++ b/capnpc-csharp.tests/capnpc-csharp.tests.csproj @@ -0,0 +1,40 @@ + + + + netcoreapp2.2 + capnpc_csharp.Tests + + false + + + + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + diff --git a/capnpc-csharp/Generator/CodeGenerator.cs b/capnpc-csharp/Generator/CodeGenerator.cs index 785a50f..e401b3a 100644 --- a/capnpc-csharp/Generator/CodeGenerator.cs +++ b/capnpc-csharp/Generator/CodeGenerator.cs @@ -33,6 +33,8 @@ _interfaceGen = new InterfaceSnippetGen(_names); } + internal GenNames GetNames() => _names; + IEnumerable TransformEnum(TypeDefinition def) { yield return _commonGen.MakeEnum(def); @@ -102,7 +104,7 @@ yield return _interfaceGen.MakePipeliningSupport(def); } - if (def.NestedTypes.Count > 0) + if (def.NestedTypes.Any()) { var ns = ClassDeclaration( _names.MakeTypeName(def, NameUsage.Namespace).ToString()) @@ -143,20 +145,11 @@ } } - string Transform(GenFile file) + internal string Transform(GenFile file) { - if (file.Namespace != null) - { - _names.TopNamespace = IdentifierName(MakeCamel(file.Namespace[0])); + NameSyntax topNamespace = GenNames.NamespaceName(file.Namespace) ?? _names.TopNamespace; - foreach (string name in file.Namespace.Skip(1)) - { - var temp = IdentifierName(MakeCamel(name)); - _names.TopNamespace = QualifiedName(_names.TopNamespace, temp); - } - } - - var ns = NamespaceDeclaration(_names.TopNamespace); + var ns = NamespaceDeclaration(topNamespace); foreach (var def in file.NestedTypes) { diff --git a/capnpc-csharp/Generator/GenNames.cs b/capnpc-csharp/Generator/GenNames.cs index f71a253..f842413 100644 --- a/capnpc-csharp/Generator/GenNames.cs +++ b/capnpc-csharp/Generator/GenNames.cs @@ -213,29 +213,24 @@ namespace CapnpC.Generator } } - NameSyntax GetQName(TypeDefinition def) + public static NameSyntax NamespaceName(string[] @namespace) { - var stack = new Stack(); - - stack.Push(MakeGenericTypeName(def, NameUsage.Default)); - - while (def.DeclaringElement is TypeDefinition pdef) + NameSyntax ident = null; + if (@namespace != null) { - stack.Push(MakeGenericTypeName(pdef, NameUsage.Namespace)); - def = pdef; + ident = IdentifierName(SyntaxHelpers.MakeCamel(@namespace[0])); + foreach (string name in @namespace.Skip(1)) + { + var temp = IdentifierName(SyntaxHelpers.MakeCamel(name)); + ident = QualifiedName(ident, temp); + } } - - var qtype = TopNamespace; - - foreach (var name in stack) - { - qtype = QualifiedName(qtype, name); - } - - return qtype; + return ident; } - NameSyntax GetQName(Model.Type type, TypeDefinition scope) + NameSyntax GetNamespaceFor(TypeDefinition def) => NamespaceName(def?.File?.Namespace); + + internal NameSyntax GetQName(Model.Type type, TypeDefinition scope) { // FIXME: With the help of the 'scope' parameter we will be able to generate abbreviated // qualified names. Unfortunately the commented approach is too naive. It will fail if @@ -262,7 +257,10 @@ namespace CapnpC.Generator def = pdef; } - var qtype = TopNamespace; + var qtype = + GetNamespaceFor(type.Definition) + ?? GetNamespaceFor(scope) + ?? TopNamespace; foreach (var name in stack) { @@ -563,7 +561,8 @@ namespace CapnpC.Generator } var typeNames = new HashSet(def.NestedTypes.Select(t => MakeTypeName(t))); - + typeNames.Add(MakeTypeName(def)); + foreach (var member in def.Fields) { var memberName = new Name(SyntaxHelpers.MakeCamel(member.Name)); diff --git a/capnpc-csharp/Model/Annotation.cs b/capnpc-csharp/Model/Annotation.cs new file mode 100644 index 0000000..6dbbd9a --- /dev/null +++ b/capnpc-csharp/Model/Annotation.cs @@ -0,0 +1,19 @@ + +namespace CapnpC.Model +{ + class Annotation : IDefinition + { + public ulong Id { get; } + public TypeTag Tag { get => TypeTag.Annotation; } + public IHasNestedDefinitions DeclaringElement { get; } + + public Type Type { get; set; } + + public Annotation(ulong id, IHasNestedDefinitions parent) + { + Id = id; + DeclaringElement = parent; + parent.NestedDefinitions.Add(this); + } + } +} diff --git a/capnpc-csharp/Model/Constant.cs b/capnpc-csharp/Model/Constant.cs new file mode 100644 index 0000000..a992ca0 --- /dev/null +++ b/capnpc-csharp/Model/Constant.cs @@ -0,0 +1,19 @@ + +namespace CapnpC.Model +{ + class Constant : IDefinition + { + public ulong Id { get; } + public TypeTag Tag { get => TypeTag.Const; } + public IHasNestedDefinitions DeclaringElement { get; } + + public Value Value { get; set; } + + public Constant(ulong id, IHasNestedDefinitions parent) + { + Id = id; + DeclaringElement = parent; + parent.NestedDefinitions.Add(this); + } + } +} diff --git a/capnpc-csharp/Model/DefinitionManager.cs b/capnpc-csharp/Model/DefinitionManager.cs new file mode 100644 index 0000000..4a88fc6 --- /dev/null +++ b/capnpc-csharp/Model/DefinitionManager.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace CapnpC.Model +{ + class DefinitionManager + { + readonly Dictionary _id2def = new Dictionary(); + + public GenFile CreateFile(ulong id) + => CreateId(id, () => new GenFile(id)); + public GenFile GetExistingFile(ulong id) + => GetId(id, TypeTag.File); + + public TypeDefinition CreateTypeDef(ulong id, TypeTag tag, IHasNestedDefinitions decl) + => CreateId(id, () => new TypeDefinition(tag, id, decl)); + public TypeDefinition GetExistingTypeDef(ulong id, TypeTag tag) + { + var def = GetId(id, tag); + if (def.Tag == TypeTag.Unknown) def.Tag = tag; + return def; + } + + public Annotation CreateAnnotation(ulong id, IHasNestedDefinitions decl) + => CreateId(id, () => new Annotation(id, decl)); + public Annotation GetExistingAnnotation(ulong id) + => GetId(id, TypeTag.Annotation); + + public Constant CreateConstant(ulong id, IHasNestedDefinitions decl) + => CreateId(id, () => new Constant(id, decl)); + public Constant GetExistingConstant(ulong id) + => GetId(id, TypeTag.Const); + + public IDefinition GetExistingDef(ulong id, TypeTag tag) + => GetId(id, tag); + + public IEnumerable Files + { + get => _id2def.Values.Where(d => d.Tag == TypeTag.File).Select(f => f as GenFile); + } + + T CreateId(ulong id, Func creator) where T : class, IDefinition + { + if (_id2def.TryGetValue(id, out var d)) + { + throw new ArgumentException(nameof(id), $"Attempting to redefine {d.Tag.ToString()} {id.StrId()} (as {nameof(T)})."); + } + var def = creator(); + _id2def.Add(id, def); + return def as T; + } + + T GetId(ulong id, TypeTag tag) where T : IDefinition + { + if (!_id2def.TryGetValue(id, out var anyDef)) + { + throw new ArgumentOutOfRangeException($"Attempting to retrieve nonexistent node {id.StrId()}."); + } + if (!(anyDef is T def) || (tag != TypeTag.Unknown && def.Tag != tag)) + { + throw new ArgumentOutOfRangeException($"Attempting to retrieve {tag.ToString()} {id.StrId()}, but found {anyDef.Tag.ToString()} instead."); + } + return def; + } + } +} diff --git a/capnpc-csharp/Model/Enumerant.cs b/capnpc-csharp/Model/Enumerant.cs index 99d53fc..96d9137 100644 --- a/capnpc-csharp/Model/Enumerant.cs +++ b/capnpc-csharp/Model/Enumerant.cs @@ -2,8 +2,12 @@ { class Enumerant { + string _literal; public TypeDefinition TypeDefinition { get; set; } - public string Literal { get; set; } + public string Literal { + get => _literal; + set => _literal = IdentifierRenamer.ToNonKeyword(value); + } public ushort? Ordinal { get; set; } public int CodeOrder { get; set; } } diff --git a/capnpc-csharp/Model/GenFile.cs b/capnpc-csharp/Model/GenFile.cs index 045c975..f912303 100644 --- a/capnpc-csharp/Model/GenFile.cs +++ b/capnpc-csharp/Model/GenFile.cs @@ -1,15 +1,23 @@ -using System; -using System.Collections.Generic; -using System.Text; +using System.Collections.Generic; namespace CapnpC.Model { - class GenFile: IHasNestedDefinitions + class GenFile: IDefinition, IHasNestedDefinitions { + public ulong Id { get; } + public TypeTag Tag { get => TypeTag.File; } + public IHasNestedDefinitions DeclaringElement { get; } + public string Name { get; set; } public string[] Namespace { get; set; } - public List NestedTypes { get; } = new List(); - public List Constants { get; } = new List(); + public IEnumerable NestedTypes { get => this.GetNestedTypes(); } + public ICollection NestedDefinitions { get; } = new List(); + public ICollection Constants { get; } = new List(); + + public GenFile(ulong id) + { + Id = id; + } } } diff --git a/capnpc-csharp/Model/IDefinition.cs b/capnpc-csharp/Model/IDefinition.cs new file mode 100644 index 0000000..e226d40 --- /dev/null +++ b/capnpc-csharp/Model/IDefinition.cs @@ -0,0 +1,10 @@ + +namespace CapnpC.Model +{ + interface IDefinition + { + ulong Id { get; } + TypeTag Tag { get; } + IHasNestedDefinitions DeclaringElement { get; } + } +} diff --git a/capnpc-csharp/Model/IHasNestedDefinitions.cs b/capnpc-csharp/Model/IHasNestedDefinitions.cs index e9e030b..3885c60 100644 --- a/capnpc-csharp/Model/IHasNestedDefinitions.cs +++ b/capnpc-csharp/Model/IHasNestedDefinitions.cs @@ -1,10 +1,18 @@ using System.Collections.Generic; +using System.Linq; namespace CapnpC.Model { interface IHasNestedDefinitions { - List NestedTypes { get; } - List Constants { get; } + IEnumerable NestedTypes { get; } + ICollection NestedDefinitions { get; } + ICollection Constants { get; } + } + + static partial class Extensions + { + public static IEnumerable GetNestedTypes(this IHasNestedDefinitions def) + => def.NestedDefinitions.Select(d => d as TypeDefinition).Where(d => d != null); } } diff --git a/capnpc-csharp/Model/IdentifierRenamer.cs b/capnpc-csharp/Model/IdentifierRenamer.cs new file mode 100644 index 0000000..9c6ff07 --- /dev/null +++ b/capnpc-csharp/Model/IdentifierRenamer.cs @@ -0,0 +1,22 @@ +using Microsoft.CodeAnalysis.CSharp; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CapnpC.Model +{ + public class IdentifierRenamer + { + public static bool IsAnyKeyword(string str) + { + return SyntaxFacts.GetKeywordKind(str) != SyntaxKind.None + || SyntaxFacts.GetContextualKeywordKind(str) != SyntaxKind.None; + } + public static string ToNonKeyword(string str) + { + // Capnp schema identifiers should be already valid, but could be a keyword + if (IsAnyKeyword(str)) return $"@{str}"; + return str; + } + } +} diff --git a/capnpc-csharp/Model/SchemaModel.cs b/capnpc-csharp/Model/SchemaModel.cs index 0e6b5be..4ebafec 100644 --- a/capnpc-csharp/Model/SchemaModel.cs +++ b/capnpc-csharp/Model/SchemaModel.cs @@ -9,30 +9,30 @@ namespace CapnpC.Model class SchemaModel { readonly Schema.CodeGeneratorRequest.Reader _request; - readonly List _files = new List(); - readonly Stack _typeNest = new Stack(); - readonly TypeDefinitionManager _typeDefMgr = new TypeDefinitionManager(); - Method _currentMethod; + readonly List _generatedFiles = new List(); + readonly DefinitionManager _typeDefMgr = new DefinitionManager(); - Dictionary _id2node; + readonly Dictionary _id2node = new Dictionary(); public SchemaModel(Schema.CodeGeneratorRequest.Reader request) { _request = request; } - public IReadOnlyList FilesToGenerate => _files; + public IReadOnlyList FilesToGenerate => _generatedFiles; + + Schema.Node.Reader? IdToNode(ulong id, bool mustExist) + { + if (_id2node.TryGetValue(id, out var node)) + return node; + if (mustExist) + throw new InvalidSchemaException($"Node with ID {id.StrId()} is required by the codegen backend but is missing."); + return null; + } Schema.Node.Reader IdToNode(ulong id) { - try - { - return _id2node[id]; - } - catch (KeyNotFoundException) - { - throw new InvalidSchemaException($"Node with ID {id} is missing"); - } + return (Schema.Node.Reader)IdToNode(id, true); } void Build() @@ -42,69 +42,178 @@ namespace CapnpC.Model throw new InvalidSchemaException("No nodes, nothing to generate"); } - try + foreach (var node in _request.Nodes) { - _id2node = _request.Nodes.ToDictionary(n => n.Id); - } - catch (ArgumentException) - { - throw new InvalidSchemaException("Nodes with duplicate IDs detected"); - } - - foreach (var reqFile in _request.RequestedFiles) - { - var file = new GenFile() + if (_id2node.TryGetValue(node.Id, out var existingNode)) { - Name = reqFile.Filename - }; + throw new InvalidSchemaException($"Node {node.StrId()} \"{node.DisplayName}\" has a duplicate ID, prior node was \"{existingNode.DisplayName}\""); + } + _id2node[node.Id] = node; + } - _files.Add(file); - _typeNest.Push(file); + var requestedFiles = _request.RequestedFiles.ToDictionary(req => req.Id); + BuildPass1(requestedFiles); + BuildPass2(requestedFiles); + } - var fileNode = IdToNode(reqFile.Id); + // First pass: create type definitions for each node. - if (!fileNode.IsFile) - throw new InvalidSchemaException("Expected a file node"); + struct Pass1State + { + public HashSet unprocessedNodes; + public bool isGenerated; + public IHasNestedDefinitions parent; + } - ProcessFile(fileNode); - - _typeNest.Pop(); + void BuildPass1(Dictionary requestedFiles) + { + Pass1State state = new Pass1State() + { + unprocessedNodes = new HashSet(_id2node.Keys) + }; + foreach (var node in _id2node.Values.Where(n => n.IsFile)) + { + GenFile file; + state.isGenerated = requestedFiles.TryGetValue(node.Id, out var req); + state.parent = null; + if (state.isGenerated) + { + file = (GenFile)ProcessNodePass1(node.Id, req.Filename, state); + _generatedFiles.Add(file); + } + else + { + file = (GenFile)ProcessNodePass1(node.Id, node.DisplayName, state); + } + } + if (state.unprocessedNodes.Count != 0) + { + throw new InvalidSchemaException("Unreferenced nodes were present in the schema."); } } - void ProcessFile(Schema.Node.Reader fileReader) + IDefinition ProcessNodePass1(ulong id, string name, Pass1State state) { - foreach (var annotation in fileReader.Annotations) + if (!(IdToNode(id, state.isGenerated) is Schema.Node.Reader node)) + return null; + if (!state.unprocessedNodes.Remove(id)) + return null; + + IDefinition def = null; + bool processNestedNodes = false; + bool processFields = false; + bool processInterfaceMethods = false; + + switch (node.GetKind()) + { + case NodeKind.Annotation: + return _typeDefMgr.CreateAnnotation(id, state.parent); + case NodeKind.Const: + return _typeDefMgr.CreateConstant(id, state.parent); + case NodeKind.File: + if (state.parent != null) + throw new InvalidSchemaException("Did not expect file nodes to appear as nested nodes"); + var file = _typeDefMgr.CreateFile(id); + file.Namespace = GetNamespaceAnnotation(node); + file.Name = name; + def = file; + processNestedNodes = true; + break; + case NodeKind.Enum: + break; + case NodeKind.Interface: + processNestedNodes = true; + processFields = true; + processInterfaceMethods = true; + break; + case NodeKind.Struct: + case NodeKind.Group: + processNestedNodes = true; + processFields = true; + break; + default: + throw new InvalidSchemaException($"Don't know how to process node {node.StrId()} \"{node.DisplayName}\""); + } + + if (def == null) + { + var typeDef = _typeDefMgr.CreateTypeDef(id, node.GetTypeTag(), state.parent); + typeDef.Name = name; + def = typeDef; + } + state.parent = def as IHasNestedDefinitions; + + if (processNestedNodes && node.NestedNodes != null) + foreach (var nested in node.NestedNodes) + { + ProcessNodePass1(nested.Id, nested.Name, state); + } + if (processFields && node.Fields != null) + foreach (var field in node.Fields.Where(f => f.IsGroup)) + { + var group = IdToNode(field.Group_TypeId); + if (!group.IsStruct || !group.Struct_IsGroup) + { + throw new InvalidSchemaException($"Expected node with id {group.StrId()} to be a struct definition"); + } + ProcessNodePass1(field.Group_TypeId, field.Name, state); + } + if (processInterfaceMethods && node.Interface_Methods != null) + foreach (var method in node.Interface_Methods) + { + var pnode = IdToNode(method.ParamStructType); + if (pnode.ScopeId == 0) ProcessNodePass1(pnode.Id, null, state); // Anonymous generated type + pnode = IdToNode(method.ResultStructType); + if (pnode.ScopeId == 0) ProcessNodePass1(pnode.Id, null, state); // Anonymous generated type + } + return def; + } + + string[] GetNamespaceAnnotation(Schema.Node.Reader fileNode) + { + foreach (var annotation in fileNode.Annotations) { if (annotation.Id == 0xb9c6f99ebf805f2c) // Cxx namespace { - ((GenFile)_typeNest.Peek()).Namespace = annotation.Value.Text.Split("::"); + return annotation.Value.Text.Split(new string[1] { "::" }, default); } } + return null; + } - foreach (var nestedNode in fileReader.NestedNodes) + // 2nd pass: Generate types based on definitions + + struct Pass2State + { + public Method currentMethod; + public bool isGenerated; + public HashSet processedNodes; + } + + void BuildPass2(Dictionary requestedFiles) + { + var state = new Pass2State() { processedNodes = new HashSet() }; + foreach (var file in _typeDefMgr.Files) { - var node = IdToNode(nestedNode.Id); - - ProcessNode(node, nestedNode.Name); + var node = IdToNode(file.Id); + state.isGenerated = requestedFiles.ContainsKey(file.Id); + ProcessNestedNodes(node.NestedNodes, state); } } - TypeDefinition GetOrCreateTypeDef(ulong id, TypeTag tag) => _typeDefMgr.GetOrCreate(id, tag); - TypeDefinition GetGroupTypeDef(ulong id, string name) + void ProcessNestedNodes(IEnumerable nestedNodes, Pass2State state) { - var nodeReader = _id2node[id]; - - if (!nodeReader.IsStruct) - throw new InvalidSchemaException($"Expected node with id {id} to be a struct definition"); - - return ProcessStruct(nodeReader, name); + foreach (var nestedNode in nestedNodes) + { + ProcessNode(nestedNode.Id, state); + } } - void ProcessBrand(Schema.Brand.Reader brandReader, Type type) + + void ProcessBrand(Schema.Brand.Reader brandReader, Type type, Pass2State state) { foreach (var scopeReader in brandReader.Scopes) { - var whatToBind = GetOrCreateTypeDef(scopeReader.ScopeId, TypeTag.Unknown); + var whatToBind = ProcessTypeDef(scopeReader.ScopeId, state); int index = 0; switch (0) @@ -121,7 +230,7 @@ namespace CapnpC.Model switch (0) { case 0 when bindingReader.IsType: - type.BindGenericParameter(typeParameter, ProcessType(bindingReader.Type)); + type.BindGenericParameter(typeParameter, ProcessType(bindingReader.Type, state)); break; case 0 when bindingReader.IsUnbound: @@ -146,7 +255,8 @@ namespace CapnpC.Model } } } - Type ProcessType(Schema.Type.Reader typeReader) + + Type ProcessType(Schema.Type.Reader typeReader, Pass2State state) { Type result; @@ -159,7 +269,7 @@ namespace CapnpC.Model return Types.FromParameter( new GenericParameter() { - DeclaringEntity = GetOrCreateTypeDef(typeReader.AnyPointer_Parameter_ScopeId, TypeTag.Unknown), + DeclaringEntity = ProcessTypeDef(typeReader.AnyPointer_Parameter_ScopeId, state), Index = typeReader.AnyPointer_Parameter_ParameterIndex }); @@ -167,7 +277,7 @@ namespace CapnpC.Model return Types.FromParameter( new GenericParameter() { - DeclaringEntity = _currentMethod ?? throw new InvalidOperationException("current method not set"), + DeclaringEntity = state.currentMethod ?? throw new InvalidOperationException("current method not set"), Index = typeReader.AnyPointer_ImplicitMethodParameter_ParameterIndex }); @@ -205,7 +315,7 @@ namespace CapnpC.Model return Types.F64; case 0 when typeReader.IsEnum: - return Types.FromDefinition(GetOrCreateTypeDef(typeReader.Enum_TypeId, TypeTag.Enum)); + return Types.FromDefinition(ProcessTypeDef(typeReader.Enum_TypeId, state, TypeTag.Enum)); case 0 when typeReader.IsFloat32: return Types.F32; @@ -223,16 +333,16 @@ namespace CapnpC.Model return Types.S8; case 0 when typeReader.IsInterface: - result = Types.FromDefinition(GetOrCreateTypeDef(typeReader.Interface_TypeId, TypeTag.Interface)); - ProcessBrand(typeReader.Interface_Brand, result); + result = Types.FromDefinition(ProcessTypeDef(typeReader.Interface_TypeId, state, TypeTag.Interface)); + ProcessBrand(typeReader.Interface_Brand, result, state); return result; case 0 when typeReader.IsList: - return Types.List(ProcessType(typeReader.List_ElementType)); + return Types.List(ProcessType(typeReader.List_ElementType, state)); case 0 when typeReader.IsStruct: - result = Types.FromDefinition(GetOrCreateTypeDef(typeReader.Struct_TypeId, TypeTag.Struct)); - ProcessBrand(typeReader.Struct_Brand, result); + result = Types.FromDefinition(ProcessTypeDef(typeReader.Struct_TypeId, state, TypeTag.Struct)); + ProcessBrand(typeReader.Struct_Brand, result, state); return result; case 0 when typeReader.IsText: @@ -365,7 +475,7 @@ namespace CapnpC.Model return value; } - void ProcessFields(Schema.Node.Reader reader, TypeDefinition declaringType, List fields) + void ProcessFields(Schema.Node.Reader reader, TypeDefinition declaringType, List fields, Pass2State state) { if (reader.Fields == null) { @@ -389,15 +499,15 @@ namespace CapnpC.Model switch (0) { case 0 when fieldReader.IsGroup: - field.Type = Types.FromDefinition(GetGroupTypeDef( - fieldReader.Group_TypeId, fieldReader.Name)); + var def = ProcessTypeDef(fieldReader.Group_TypeId, state, TypeTag.Group); + field.Type = Types.FromDefinition(def); break; case 0 when fieldReader.IsSlot: field.DefaultValue = ProcessValue(fieldReader.Slot_DefaultValue); field.DefaultValueIsExplicit = fieldReader.Slot_HadExplicitDefault; field.Offset = fieldReader.Slot_Offset; - field.Type = ProcessType(fieldReader.Slot_Type); + field.Type = ProcessType(fieldReader.Slot_Type, state); field.DefaultValue.Type = field.Type; break; @@ -411,7 +521,7 @@ namespace CapnpC.Model } } - void ProcessInterfaceOrStructTail(TypeDefinition def, Schema.Node.Reader reader) + TypeDefinition ProcessInterfaceOrStructTail(TypeDefinition def, Schema.Node.Reader reader, Pass2State state) { def.IsGeneric = reader.IsGeneric; @@ -423,18 +533,9 @@ namespace CapnpC.Model } } - _typeNest.Push(def); + ProcessNestedNodes(reader.NestedNodes, state); - if (reader.NestedNodes != null) - { - foreach (var nestedReader in reader.NestedNodes) - { - var node = IdToNode(nestedReader.Id); - ProcessNode(node, nestedReader.Name); - } - } - - ProcessFields(reader, def, def.Fields); + ProcessFields(reader, def, def.Fields, state); if (reader.IsInterface) { @@ -450,12 +551,12 @@ namespace CapnpC.Model { method.GenericParameters.Add(implicitParameterReader.Name); } - _currentMethod = method; + state.currentMethod = method; def.Methods.Add(method); var paramNode = IdToNode(methodReader.ParamStructType); - var paramType = ProcessParameterList(paramNode, methodReader.ParamBrand, method.Params); + var paramType = ProcessParameterList(paramNode, methodReader.ParamBrand, method.Params, state); if (paramType != null) { paramType.SpecialName = SpecialName.MethodParamsStruct; @@ -470,7 +571,7 @@ namespace CapnpC.Model method.ParamsStruct.InheritFreeParameters(method); var resultNode = IdToNode(methodReader.ResultStructType); - var resultType = ProcessParameterList(resultNode, methodReader.ResultBrand, method.Results); + var resultType = ProcessParameterList(resultNode, methodReader.ResultBrand, method.Results, state); if (resultType != null) { resultType.SpecialName = SpecialName.MethodResultStruct; @@ -485,24 +586,13 @@ namespace CapnpC.Model method.ResultStruct.InheritFreeParameters(method); } - _currentMethod = null; + state.currentMethod = null; } - - _typeNest.Pop(); + return def; } - TypeDefinition ProcessStruct(Schema.Node.Reader structReader, string name) + TypeDefinition ProcessStruct(Schema.Node.Reader structReader, TypeDefinition def, Pass2State state) { - var def = GetOrCreateTypeDef( - structReader.Id, - structReader.Struct_IsGroup ? TypeTag.Group : TypeTag.Struct); - - def.DeclaringElement = _typeNest.Peek(); - if (structReader.Struct_IsGroup) - ((TypeDefinition)def.DeclaringElement).NestedGroups.Add(def); - else - def.DeclaringElement.NestedTypes.Add(def); - def.Name = name; def.StructDataWordCount = structReader.Struct_DataWordCount; def.StructPointerCount = structReader.Struct_PointerCount; @@ -513,12 +603,10 @@ namespace CapnpC.Model 16u * structReader.Struct_DiscriminantOffset); } - ProcessInterfaceOrStructTail(def, structReader); - - return def; + return ProcessInterfaceOrStructTail(def, structReader, state); } - TypeDefinition ProcessParameterList(Schema.Node.Reader reader, Schema.Brand.Reader brandReader, List list) + TypeDefinition ProcessParameterList(Schema.Node.Reader reader, Schema.Brand.Reader brandReader, List list, Pass2State state) { //# If a named parameter list was specified in the method //# declaration (rather than a single struct parameter type) then a corresponding struct type is @@ -533,58 +621,38 @@ namespace CapnpC.Model throw new InvalidSchemaException("Expected a struct"); } + var def = ProcessTypeDef(reader.Id, state, TypeTag.Struct); + if (reader.ScopeId == 0) { // Auto-generated => Named parameter list - ProcessFields(reader, null, list); - return ProcessStruct(reader, null); + foreach (var field in def.Fields) list.Add(field); + return def; } else { // Single, anonymous, struct-typed parameter - var def = GetOrCreateTypeDef(reader.Id, TypeTag.Struct); var type = Types.FromDefinition(def); - ProcessBrand(brandReader, type); + ProcessBrand(brandReader, type, state); var anon = new Field() { Type = type }; list.Add(anon); return null; } } - TypeDefinition ProcessInterface(Schema.Node.Reader ifaceReader, string name) + TypeDefinition ProcessInterface(Schema.Node.Reader ifaceReader, TypeDefinition def, Pass2State state) { - var def = GetOrCreateTypeDef( - ifaceReader.Id, - TypeTag.Interface); - - def.DeclaringElement = _typeNest.Peek(); - def.DeclaringElement.NestedTypes.Add(def); - def.Name = name; - foreach (var superClassReader in ifaceReader.Interface_Superclasses) { - var superClass = GetOrCreateTypeDef( - superClassReader.Id, - TypeTag.Interface); - + var superClass = ProcessTypeDef(superClassReader.Id, state, TypeTag.Interface); def.Superclasses.Add(Types.FromDefinition(superClass)); } - ProcessInterfaceOrStructTail(def, ifaceReader); - - return def; + return ProcessInterfaceOrStructTail(def, ifaceReader, state); } - void ProcessEnum(Schema.Node.Reader enumReader, string name) + TypeDefinition ProcessEnum(Schema.Node.Reader enumReader, TypeDefinition def, Pass2State state) { - var def = GetOrCreateTypeDef(enumReader.Id, TypeTag.Enum); - - def.DeclaringElement = _typeNest.Peek(); - def.DeclaringElement.NestedTypes.Add(def); - def.Name = name; - - _typeNest.Push(def); - foreach (var fieldReader in enumReader.Enumerants) { var field = new Enumerant() @@ -601,46 +669,52 @@ namespace CapnpC.Model def.Enumerants.Add(field); } - - _typeNest.Pop(); + return def; } - void ProcessConst(Schema.Node.Reader constReader, string name) + Constant ProcessConst(Schema.Node.Reader constReader, Constant @const, Pass2State state) { var value = ProcessValue(constReader.Const_Value); - value.Type = ProcessType(constReader.Const_Type); - - _typeNest.Peek().Constants.Add(value); + value.Type = ProcessType(constReader.Const_Type, state); + @const.Value = value; + return @const; } - void ProcessNode(Schema.Node.Reader node, string name) + TypeDefinition ProcessTypeDef(ulong id, Pass2State state, TypeTag tag = default) { - switch (0) + var def = ProcessNode(id, state, tag); + var typeDef = def as TypeDefinition; + if (def == null) + throw new ArgumentException( + $"Expected node {id.StrId()} to be a TypeDefinition but got {def.GetType().Name} instead.", + nameof(id)); + return typeDef; + } + + IDefinition ProcessNode(ulong id, Pass2State state, TypeTag tag = default) + { + if (!(IdToNode(id, state.isGenerated) is Schema.Node.Reader node)) return null; + var kind = node.GetKind(); + if (tag == TypeTag.Unknown) tag = kind.GetTypeTag(); + var def = _typeDefMgr.GetExistingDef(id, tag); + if (state.processedNodes.Contains(id)) return def; + state.processedNodes.Add(id); + + switch (def) { - case 0 when node.IsAnnotation: - break; - - case 0 when node.IsConst: - ProcessConst(node, name); - break; - - case 0 when node.IsEnum: - ProcessEnum(node, name); - break; - - case 0 when node.IsFile: - throw new InvalidSchemaException("Did not expect file nodes to appear as nested nodes"); - - case 0 when node.IsInterface: - ProcessInterface(node, name); - break; - - case 0 when node.IsStruct: - ProcessStruct(node, name); - break; - + case Annotation annotation: + return annotation; + case Constant constant: + def.DeclaringElement.Constants.Add(ProcessConst(node, constant, state)); + return def; + case TypeDefinition typeDef when kind == NodeKind.Enum: + return ProcessEnum(node, typeDef, state); + case TypeDefinition typeDef when kind == NodeKind.Interface: + return ProcessInterface(node, typeDef, state); + case TypeDefinition typeDef when kind == NodeKind.Struct || kind == NodeKind.Group: + return ProcessStruct(node, typeDef, state); default: - throw new InvalidSchemaException($"Don't know how to process node {node.DisplayName}"); + throw new InvalidProgramException($"An unexpected node {node.StrId()} was found during the 2nd schema model building pass."); } } @@ -651,4 +725,55 @@ namespace CapnpC.Model return model; } } + + public enum NodeKind + { + Unknown, + Annotation, + Const, + Enum, + File, + Interface, + Struct, + Group + } + + public static class SchemaExtensions + { + public static string GetName(this Schema.Node.Reader node) + => node.DisplayName.Substring((int)node.DisplayNamePrefixLength); + + public static string StrId(this Schema.Node.Reader node) + => $"0x{node.Id.ToString("X")}"; + + public static string StrId(this ulong nodeId) + => $"0x{nodeId.ToString("X")}"; + + public static NodeKind GetKind(this Schema.Node.Reader node) + { + if (node.IsStruct) + return node.Struct_IsGroup ? NodeKind.Group : NodeKind.Struct; + if (node.IsInterface) return NodeKind.Interface; + if (node.IsEnum) return NodeKind.Enum; + if (node.IsConst) return NodeKind.Const; + if (node.IsAnnotation) return NodeKind.Annotation; + if (node.IsFile) return NodeKind.File; + return NodeKind.Unknown; + } + + internal static TypeTag GetTypeTag(this NodeKind kind) + { + switch (kind) + { + case NodeKind.Enum: return TypeTag.Enum; + case NodeKind.Interface: return TypeTag.Interface; + case NodeKind.Struct: return TypeTag.Struct; + case NodeKind.Group: return TypeTag.Group; + default: return TypeTag.Unknown; + } + } + + internal static TypeTag GetTypeTag(this Schema.Node.Reader node) + => node.GetKind().GetTypeTag(); + } } diff --git a/capnpc-csharp/Model/Type.cs b/capnpc-csharp/Model/Type.cs index 3512b2b..c1017df 100644 --- a/capnpc-csharp/Model/Type.cs +++ b/capnpc-csharp/Model/Type.cs @@ -9,9 +9,16 @@ namespace CapnpC.Model { class Type: AbstractType { + // Representation of a type expression in the schema language + public TypeDefinition Definition { get; set; } + // The model for all nodes that are not file nodes - they define types + public GenericParameter Parameter { get; set; } + // A reference to type parameter in this scope + public Type ElementType { get; set; } + // The type of a list element, if this is a list. readonly Dictionary _parameterBindings = new Dictionary(); diff --git a/capnpc-csharp/Model/TypeDefinition.cs b/capnpc-csharp/Model/TypeDefinition.cs index ce00e2c..910d4bd 100644 --- a/capnpc-csharp/Model/TypeDefinition.cs +++ b/capnpc-csharp/Model/TypeDefinition.cs @@ -2,7 +2,7 @@ using System.Linq; namespace CapnpC.Model { - class TypeDefinition : AbstractType, IHasNestedDefinitions, IHasGenericParameters + class TypeDefinition : AbstractType, IDefinition, IHasNestedDefinitions, IHasGenericParameters { public class DiscriminationInfo { @@ -16,23 +16,30 @@ namespace CapnpC.Model public uint TagOffset { get; } } - public TypeDefinition(TypeTag tag, ulong id) + public TypeDefinition(TypeTag tag, ulong id, IHasNestedDefinitions parent) { Tag = tag; Id = id; + DeclaringElement = parent; + if (tag == TypeTag.Group) + ((TypeDefinition)parent).NestedGroups.Add(this); + else + parent.NestedDefinitions.Add(this); } public ulong Id { get; } - public IHasNestedDefinitions DeclaringElement { get; set; } + public IHasNestedDefinitions DeclaringElement { get; } + public Method UsingMethod { get; set; } public string Name { get; set; } public SpecialName SpecialName { get; set; } public DiscriminationInfo UnionInfo { get; set; } public new List Fields => base.Fields; public List Enumerants { get; } = new List(); - public List NestedTypes { get; } = new List(); + public ICollection NestedDefinitions { get; } = new List(); + public IEnumerable NestedTypes { get => this.GetNestedTypes(); } public List NestedGroups { get; } = new List(); - public List Constants { get; } = new List(); + public ICollection Constants { get; } = new List(); public List Methods { get; } = new List(); public List Superclasses { get; } = new List(); public List GenericParameters { get; } = new List(); @@ -54,6 +61,16 @@ namespace CapnpC.Model } } + public GenFile File + { + get + { + IHasNestedDefinitions cur = this; + while (cur is TypeDefinition def) cur = def.DeclaringElement; + return cur as GenFile; + } + } + public IEnumerable AllTypeParameters { get diff --git a/capnpc-csharp/Model/TypeDefinitionManager.cs b/capnpc-csharp/Model/TypeDefinitionManager.cs deleted file mode 100644 index 7c1562e..0000000 --- a/capnpc-csharp/Model/TypeDefinitionManager.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace CapnpC.Model -{ - class TypeDefinitionManager - { - readonly Dictionary _id2def = - new Dictionary(); - - public TypeDefinition GetOrCreate(ulong id, TypeTag tag) - { - if (_id2def.TryGetValue(id, out var def)) - { - if (def.Tag == TypeTag.Unknown) - { - def.Tag = tag; - } - else if (tag != TypeTag.Unknown && def.Tag != tag) - { - throw new ArgumentOutOfRangeException(nameof(tag), "Type tag does not match existing type"); - } - } - else - { - def = new TypeDefinition(tag, id); - _id2def.Add(id, def); - } - - return def; - } - - public TypeDefinition GetExisting(ulong id) - { - return _id2def[id]; - } - } -} diff --git a/capnpc-csharp/Model/TypeTag.cs b/capnpc-csharp/Model/TypeTag.cs index 66b7d5e..122b6d7 100644 --- a/capnpc-csharp/Model/TypeTag.cs +++ b/capnpc-csharp/Model/TypeTag.cs @@ -28,6 +28,9 @@ Group, Interface, Enum, - AnyEnum + AnyEnum, + Const, + Annotation, + File } } diff --git a/capnpc-csharp/Program.cs b/capnpc-csharp/Program.cs index 4da2ad2..fe758f2 100644 --- a/capnpc-csharp/Program.cs +++ b/capnpc-csharp/Program.cs @@ -1,6 +1,9 @@ using Capnp; using System; using System.IO; +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleToAttribute("capnpc-csharp.tests")] namespace CapnpC { diff --git a/capnpc-csharp/Schema/schema-with-offsets.capnp b/capnpc-csharp/Schema/schema-with-offsets.capnp index 709eaab..45fe760 100644 --- a/capnpc-csharp/Schema/schema-with-offsets.capnp +++ b/capnpc-csharp/Schema/schema-with-offsets.capnp @@ -1,6 +1,6 @@ -# F:/Downloads/capnproto-c++-win32-0.7.0/capnproto-c++-0.7.0/src/capnp/schema-priv.capnp +# schema.capnp @0xa93fc509624c72d9; -$import "/F:/Downloads/capnproto-c++-win32-0.7.0/capnproto-c++-0.7.0/src/capnp/c++.capnp".namespace("capnp::schema"); +$import "/capnp/c++.capnp".namespace("capnp::schema"); struct Node @0xe682ab4cf923a417 { # 40 bytes, 6 ptrs id @0 :UInt64; # bits[0, 64) displayName @1 :Text; # ptr[0]