Merge pull request #8 from Bertec/feature/codegen-fixes-1

Backend Fixes Part 1
This commit is contained in:
c80k 2019-08-29 21:22:43 +02:00 committed by GitHub
commit e68be281ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 997 additions and 247 deletions

View File

@ -11,6 +11,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Capnp.Net.Runtime.Tests.Std
EndProject 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}" 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 EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "capnpc-csharp.tests", "capnpc-csharp.tests\capnpc-csharp.tests.csproj", "{B77AC567-E232-4072-85C3-8689566BF3D4}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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}.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.ActiveCfg = Release|Any CPU
{58E8FFC8-D207-4B0F-842A-58ED9D3D9EEF}.Release|Any CPU.Build.0 = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@ -0,0 +1,123 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 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.
// </auto-generated>
//------------------------------------------------------------------------------
namespace capnpc_csharp.Tests.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// 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() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[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;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
internal static byte[] schema_with_offsets_capnp {
get {
object obj = ResourceManager.GetObject("schema_with_offsets_capnp", resourceCulture);
return ((byte[])(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
internal static byte[] UnitTest1_capnp {
get {
object obj = ResourceManager.GetObject("UnitTest1_capnp", resourceCulture);
return ((byte[])(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
internal static byte[] UnitTest10_capnp {
get {
object obj = ResourceManager.GetObject("UnitTest10_capnp", resourceCulture);
return ((byte[])(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
internal static byte[] UnitTest2_capnp {
get {
object obj = ResourceManager.GetObject("UnitTest2_capnp", resourceCulture);
return ((byte[])(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
internal static byte[] UnitTest20_capnp {
get {
object obj = ResourceManager.GetObject("UnitTest20_capnp", resourceCulture);
return ((byte[])(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
internal static byte[] UnitTest3_capnp {
get {
object obj = ResourceManager.GetObject("UnitTest3_capnp", resourceCulture);
return ((byte[])(obj));
}
}
}
}

View File

@ -0,0 +1,139 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="UnitTest10_capnp" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\UnitTest10.capnp.bin;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="UnitTest20_capnp" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\UnitTest20.capnp.bin;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="UnitTest3_capnp" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\UnitTest3.capnp.bin;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="UnitTest2_capnp" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\UnitTest2.capnp.bin;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="UnitTest1_capnp" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\UnitTest1.capnp.bin;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="schema_with_offsets_capnp" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\schema-with-offsets.capnp.bin;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
</root>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,9 @@
@0x93d3c4d19cd84a4c;
enum Enumerant {
byte @0;
}
struct Foo {
foo @0: UInt8;
}

View File

@ -0,0 +1,9 @@
@0xbbfd48ae4b99d012;
using Cxx = import "/capnp/c++.capnp";
$Cxx.namespace("Foo::Bar::Baz");
struct Outer {
inner @0: import "UnitTest10b.capnp".Inner ;
}

View File

@ -0,0 +1,7 @@
@0xaf95c1c78b01be97;
using Cxx = import "/capnp/c++.capnp";
$Cxx.namespace("Foo::Garf::Snarf");
struct Inner {}

View File

@ -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) {}

View File

@ -0,0 +1,5 @@
@0xcebb7a61a86a7492;
annotation anAnnotation(file): Text;
const aConstant :UInt16 = 42;

View File

@ -0,0 +1,6 @@
@0xb7158f7fa52b8db6;
using Cxx = import "/capnp/c++.capnp";
$Cxx.namespace("Foo.Bar.Baz");

View File

@ -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<Model.TypeDefinition> 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;
}
}
}

View File

@ -0,0 +1,40 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<RootNamespace>capnpc_csharp.Tests</RootNamespace>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.0.1" />
<PackageReference Include="MSTest.TestAdapter" Version="1.4.0" />
<PackageReference Include="MSTest.TestFramework" Version="1.4.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\capnpc-csharp\capnpc-csharp.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Remove="*.cs" />
<Compile Include="UnitTests.cs" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>

View File

@ -33,6 +33,8 @@
_interfaceGen = new InterfaceSnippetGen(_names); _interfaceGen = new InterfaceSnippetGen(_names);
} }
internal GenNames GetNames() => _names;
IEnumerable<MemberDeclarationSyntax> TransformEnum(TypeDefinition def) IEnumerable<MemberDeclarationSyntax> TransformEnum(TypeDefinition def)
{ {
yield return _commonGen.MakeEnum(def); yield return _commonGen.MakeEnum(def);
@ -102,7 +104,7 @@
yield return _interfaceGen.MakePipeliningSupport(def); yield return _interfaceGen.MakePipeliningSupport(def);
} }
if (def.NestedTypes.Count > 0) if (def.NestedTypes.Any())
{ {
var ns = ClassDeclaration( var ns = ClassDeclaration(
_names.MakeTypeName(def, NameUsage.Namespace).ToString()) _names.MakeTypeName(def, NameUsage.Namespace).ToString())
@ -143,20 +145,11 @@
} }
} }
string Transform(GenFile file) internal string Transform(GenFile file)
{ {
if (file.Namespace != null) NameSyntax topNamespace = GenNames.NamespaceName(file.Namespace) ?? _names.TopNamespace;
{
_names.TopNamespace = IdentifierName(MakeCamel(file.Namespace[0]));
foreach (string name in file.Namespace.Skip(1)) var ns = NamespaceDeclaration(topNamespace);
{
var temp = IdentifierName(MakeCamel(name));
_names.TopNamespace = QualifiedName(_names.TopNamespace, temp);
}
}
var ns = NamespaceDeclaration(_names.TopNamespace);
foreach (var def in file.NestedTypes) foreach (var def in file.NestedTypes)
{ {

View File

@ -213,29 +213,24 @@ namespace CapnpC.Generator
} }
} }
NameSyntax GetQName(TypeDefinition def) public static NameSyntax NamespaceName(string[] @namespace)
{ {
var stack = new Stack<SimpleNameSyntax>(); NameSyntax ident = null;
if (@namespace != null)
stack.Push(MakeGenericTypeName(def, NameUsage.Default));
while (def.DeclaringElement is TypeDefinition pdef)
{ {
stack.Push(MakeGenericTypeName(pdef, NameUsage.Namespace)); ident = IdentifierName(SyntaxHelpers.MakeCamel(@namespace[0]));
def = pdef; foreach (string name in @namespace.Skip(1))
{
var temp = IdentifierName(SyntaxHelpers.MakeCamel(name));
ident = QualifiedName(ident, temp);
}
} }
return ident;
var qtype = TopNamespace;
foreach (var name in stack)
{
qtype = QualifiedName(qtype, name);
}
return qtype;
} }
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 // 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 // qualified names. Unfortunately the commented approach is too naive. It will fail if
@ -262,7 +257,10 @@ namespace CapnpC.Generator
def = pdef; def = pdef;
} }
var qtype = TopNamespace; var qtype =
GetNamespaceFor(type.Definition)
?? GetNamespaceFor(scope)
?? TopNamespace;
foreach (var name in stack) foreach (var name in stack)
{ {
@ -563,7 +561,8 @@ namespace CapnpC.Generator
} }
var typeNames = new HashSet<Name>(def.NestedTypes.Select(t => MakeTypeName(t))); var typeNames = new HashSet<Name>(def.NestedTypes.Select(t => MakeTypeName(t)));
typeNames.Add(MakeTypeName(def));
foreach (var member in def.Fields) foreach (var member in def.Fields)
{ {
var memberName = new Name(SyntaxHelpers.MakeCamel(member.Name)); var memberName = new Name(SyntaxHelpers.MakeCamel(member.Name));

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace CapnpC.Model
{
class DefinitionManager
{
readonly Dictionary<ulong, IDefinition> _id2def = new Dictionary<ulong, IDefinition>();
public GenFile CreateFile(ulong id)
=> CreateId<GenFile>(id, () => new GenFile(id));
public GenFile GetExistingFile(ulong id)
=> GetId<GenFile>(id, TypeTag.File);
public TypeDefinition CreateTypeDef(ulong id, TypeTag tag, IHasNestedDefinitions decl)
=> CreateId<TypeDefinition>(id, () => new TypeDefinition(tag, id, decl));
public TypeDefinition GetExistingTypeDef(ulong id, TypeTag tag)
{
var def = GetId<TypeDefinition>(id, tag);
if (def.Tag == TypeTag.Unknown) def.Tag = tag;
return def;
}
public Annotation CreateAnnotation(ulong id, IHasNestedDefinitions decl)
=> CreateId<Annotation>(id, () => new Annotation(id, decl));
public Annotation GetExistingAnnotation(ulong id)
=> GetId<Annotation>(id, TypeTag.Annotation);
public Constant CreateConstant(ulong id, IHasNestedDefinitions decl)
=> CreateId<Constant>(id, () => new Constant(id, decl));
public Constant GetExistingConstant(ulong id)
=> GetId<Constant>(id, TypeTag.Const);
public IDefinition GetExistingDef(ulong id, TypeTag tag)
=> GetId<IDefinition>(id, tag);
public IEnumerable<GenFile> Files
{
get => _id2def.Values.Where(d => d.Tag == TypeTag.File).Select(f => f as GenFile);
}
T CreateId<T>(ulong id, Func<IDefinition> 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<T>(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;
}
}
}

View File

@ -2,8 +2,12 @@
{ {
class Enumerant class Enumerant
{ {
string _literal;
public TypeDefinition TypeDefinition { get; set; } 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 ushort? Ordinal { get; set; }
public int CodeOrder { get; set; } public int CodeOrder { get; set; }
} }

View File

@ -1,15 +1,23 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Text;
namespace CapnpC.Model 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 Name { get; set; }
public string[] Namespace { get; set; } public string[] Namespace { get; set; }
public List<TypeDefinition> NestedTypes { get; } = new List<TypeDefinition>(); public IEnumerable<TypeDefinition> NestedTypes { get => this.GetNestedTypes(); }
public List<Value> Constants { get; } = new List<Value>(); public ICollection<IDefinition> NestedDefinitions { get; } = new List<IDefinition>();
public ICollection<Constant> Constants { get; } = new List<Constant>();
public GenFile(ulong id)
{
Id = id;
}
} }
} }

View File

@ -0,0 +1,10 @@

namespace CapnpC.Model
{
interface IDefinition
{
ulong Id { get; }
TypeTag Tag { get; }
IHasNestedDefinitions DeclaringElement { get; }
}
}

View File

@ -1,10 +1,18 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
namespace CapnpC.Model namespace CapnpC.Model
{ {
interface IHasNestedDefinitions interface IHasNestedDefinitions
{ {
List<TypeDefinition> NestedTypes { get; } IEnumerable<TypeDefinition> NestedTypes { get; }
List<Value> Constants { get; } ICollection<IDefinition> NestedDefinitions { get; }
ICollection<Constant> Constants { get; }
}
static partial class Extensions
{
public static IEnumerable<TypeDefinition> GetNestedTypes(this IHasNestedDefinitions def)
=> def.NestedDefinitions.Select(d => d as TypeDefinition).Where(d => d != null);
} }
} }

View File

@ -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;
}
}
}

View File

@ -9,30 +9,30 @@ namespace CapnpC.Model
class SchemaModel class SchemaModel
{ {
readonly Schema.CodeGeneratorRequest.Reader _request; readonly Schema.CodeGeneratorRequest.Reader _request;
readonly List<GenFile> _files = new List<GenFile>(); readonly List<GenFile> _generatedFiles = new List<GenFile>();
readonly Stack<IHasNestedDefinitions> _typeNest = new Stack<IHasNestedDefinitions>(); readonly DefinitionManager _typeDefMgr = new DefinitionManager();
readonly TypeDefinitionManager _typeDefMgr = new TypeDefinitionManager();
Method _currentMethod;
Dictionary<ulong, Schema.Node.Reader> _id2node; readonly Dictionary<ulong, Schema.Node.Reader> _id2node = new Dictionary<ulong, Schema.Node.Reader>();
public SchemaModel(Schema.CodeGeneratorRequest.Reader request) public SchemaModel(Schema.CodeGeneratorRequest.Reader request)
{ {
_request = request; _request = request;
} }
public IReadOnlyList<GenFile> FilesToGenerate => _files; public IReadOnlyList<GenFile> 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) Schema.Node.Reader IdToNode(ulong id)
{ {
try return (Schema.Node.Reader)IdToNode(id, true);
{
return _id2node[id];
}
catch (KeyNotFoundException)
{
throw new InvalidSchemaException($"Node with ID {id} is missing");
}
} }
void Build() void Build()
@ -42,69 +42,178 @@ namespace CapnpC.Model
throw new InvalidSchemaException("No nodes, nothing to generate"); throw new InvalidSchemaException("No nodes, nothing to generate");
} }
try foreach (var node in _request.Nodes)
{ {
_id2node = _request.Nodes.ToDictionary(n => n.Id); if (_id2node.TryGetValue(node.Id, out var existingNode))
}
catch (ArgumentException)
{
throw new InvalidSchemaException("Nodes with duplicate IDs detected");
}
foreach (var reqFile in _request.RequestedFiles)
{
var file = new GenFile()
{ {
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); var requestedFiles = _request.RequestedFiles.ToDictionary(req => req.Id);
_typeNest.Push(file); BuildPass1(requestedFiles);
BuildPass2(requestedFiles);
}
var fileNode = IdToNode(reqFile.Id); // First pass: create type definitions for each node.
if (!fileNode.IsFile) struct Pass1State
throw new InvalidSchemaException("Expected a file node"); {
public HashSet<ulong> unprocessedNodes;
public bool isGenerated;
public IHasNestedDefinitions parent;
}
ProcessFile(fileNode); void BuildPass1(Dictionary<ulong, Schema.CodeGeneratorRequest.RequestedFile.Reader> requestedFiles)
{
_typeNest.Pop(); Pass1State state = new Pass1State()
{
unprocessedNodes = new HashSet<ulong>(_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 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<ulong> processedNodes;
}
void BuildPass2(Dictionary<ulong, Schema.CodeGeneratorRequest.RequestedFile.Reader> requestedFiles)
{
var state = new Pass2State() { processedNodes = new HashSet<ulong>() };
foreach (var file in _typeDefMgr.Files)
{ {
var node = IdToNode(nestedNode.Id); var node = IdToNode(file.Id);
state.isGenerated = requestedFiles.ContainsKey(file.Id);
ProcessNode(node, nestedNode.Name); ProcessNestedNodes(node.NestedNodes, state);
} }
} }
TypeDefinition GetOrCreateTypeDef(ulong id, TypeTag tag) => _typeDefMgr.GetOrCreate(id, tag); void ProcessNestedNodes(IEnumerable<Schema.Node.NestedNode.Reader> nestedNodes, Pass2State state)
TypeDefinition GetGroupTypeDef(ulong id, string name)
{ {
var nodeReader = _id2node[id]; foreach (var nestedNode in nestedNodes)
{
if (!nodeReader.IsStruct) ProcessNode(nestedNode.Id, state);
throw new InvalidSchemaException($"Expected node with id {id} to be a struct definition"); }
return ProcessStruct(nodeReader, name);
} }
void ProcessBrand(Schema.Brand.Reader brandReader, Type type)
void ProcessBrand(Schema.Brand.Reader brandReader, Type type, Pass2State state)
{ {
foreach (var scopeReader in brandReader.Scopes) foreach (var scopeReader in brandReader.Scopes)
{ {
var whatToBind = GetOrCreateTypeDef(scopeReader.ScopeId, TypeTag.Unknown); var whatToBind = ProcessTypeDef(scopeReader.ScopeId, state);
int index = 0; int index = 0;
switch (0) switch (0)
@ -121,7 +230,7 @@ namespace CapnpC.Model
switch (0) switch (0)
{ {
case 0 when bindingReader.IsType: case 0 when bindingReader.IsType:
type.BindGenericParameter(typeParameter, ProcessType(bindingReader.Type)); type.BindGenericParameter(typeParameter, ProcessType(bindingReader.Type, state));
break; break;
case 0 when bindingReader.IsUnbound: 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; Type result;
@ -159,7 +269,7 @@ namespace CapnpC.Model
return Types.FromParameter( return Types.FromParameter(
new GenericParameter() new GenericParameter()
{ {
DeclaringEntity = GetOrCreateTypeDef(typeReader.AnyPointer_Parameter_ScopeId, TypeTag.Unknown), DeclaringEntity = ProcessTypeDef(typeReader.AnyPointer_Parameter_ScopeId, state),
Index = typeReader.AnyPointer_Parameter_ParameterIndex Index = typeReader.AnyPointer_Parameter_ParameterIndex
}); });
@ -167,7 +277,7 @@ namespace CapnpC.Model
return Types.FromParameter( return Types.FromParameter(
new GenericParameter() 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 Index = typeReader.AnyPointer_ImplicitMethodParameter_ParameterIndex
}); });
@ -205,7 +315,7 @@ namespace CapnpC.Model
return Types.F64; return Types.F64;
case 0 when typeReader.IsEnum: 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: case 0 when typeReader.IsFloat32:
return Types.F32; return Types.F32;
@ -223,16 +333,16 @@ namespace CapnpC.Model
return Types.S8; return Types.S8;
case 0 when typeReader.IsInterface: case 0 when typeReader.IsInterface:
result = Types.FromDefinition(GetOrCreateTypeDef(typeReader.Interface_TypeId, TypeTag.Interface)); result = Types.FromDefinition(ProcessTypeDef(typeReader.Interface_TypeId, state, TypeTag.Interface));
ProcessBrand(typeReader.Interface_Brand, result); ProcessBrand(typeReader.Interface_Brand, result, state);
return result; return result;
case 0 when typeReader.IsList: 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: case 0 when typeReader.IsStruct:
result = Types.FromDefinition(GetOrCreateTypeDef(typeReader.Struct_TypeId, TypeTag.Struct)); result = Types.FromDefinition(ProcessTypeDef(typeReader.Struct_TypeId, state, TypeTag.Struct));
ProcessBrand(typeReader.Struct_Brand, result); ProcessBrand(typeReader.Struct_Brand, result, state);
return result; return result;
case 0 when typeReader.IsText: case 0 when typeReader.IsText:
@ -365,7 +475,7 @@ namespace CapnpC.Model
return value; return value;
} }
void ProcessFields(Schema.Node.Reader reader, TypeDefinition declaringType, List<Field> fields) void ProcessFields(Schema.Node.Reader reader, TypeDefinition declaringType, List<Field> fields, Pass2State state)
{ {
if (reader.Fields == null) if (reader.Fields == null)
{ {
@ -389,15 +499,15 @@ namespace CapnpC.Model
switch (0) switch (0)
{ {
case 0 when fieldReader.IsGroup: case 0 when fieldReader.IsGroup:
field.Type = Types.FromDefinition(GetGroupTypeDef( var def = ProcessTypeDef(fieldReader.Group_TypeId, state, TypeTag.Group);
fieldReader.Group_TypeId, fieldReader.Name)); field.Type = Types.FromDefinition(def);
break; break;
case 0 when fieldReader.IsSlot: case 0 when fieldReader.IsSlot:
field.DefaultValue = ProcessValue(fieldReader.Slot_DefaultValue); field.DefaultValue = ProcessValue(fieldReader.Slot_DefaultValue);
field.DefaultValueIsExplicit = fieldReader.Slot_HadExplicitDefault; field.DefaultValueIsExplicit = fieldReader.Slot_HadExplicitDefault;
field.Offset = fieldReader.Slot_Offset; field.Offset = fieldReader.Slot_Offset;
field.Type = ProcessType(fieldReader.Slot_Type); field.Type = ProcessType(fieldReader.Slot_Type, state);
field.DefaultValue.Type = field.Type; field.DefaultValue.Type = field.Type;
break; 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; def.IsGeneric = reader.IsGeneric;
@ -423,18 +533,9 @@ namespace CapnpC.Model
} }
} }
_typeNest.Push(def); ProcessNestedNodes(reader.NestedNodes, state);
if (reader.NestedNodes != null) ProcessFields(reader, def, def.Fields, state);
{
foreach (var nestedReader in reader.NestedNodes)
{
var node = IdToNode(nestedReader.Id);
ProcessNode(node, nestedReader.Name);
}
}
ProcessFields(reader, def, def.Fields);
if (reader.IsInterface) if (reader.IsInterface)
{ {
@ -450,12 +551,12 @@ namespace CapnpC.Model
{ {
method.GenericParameters.Add(implicitParameterReader.Name); method.GenericParameters.Add(implicitParameterReader.Name);
} }
_currentMethod = method; state.currentMethod = method;
def.Methods.Add(method); def.Methods.Add(method);
var paramNode = IdToNode(methodReader.ParamStructType); 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) if (paramType != null)
{ {
paramType.SpecialName = SpecialName.MethodParamsStruct; paramType.SpecialName = SpecialName.MethodParamsStruct;
@ -470,7 +571,7 @@ namespace CapnpC.Model
method.ParamsStruct.InheritFreeParameters(method); method.ParamsStruct.InheritFreeParameters(method);
var resultNode = IdToNode(methodReader.ResultStructType); 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) if (resultType != null)
{ {
resultType.SpecialName = SpecialName.MethodResultStruct; resultType.SpecialName = SpecialName.MethodResultStruct;
@ -485,24 +586,13 @@ namespace CapnpC.Model
method.ResultStruct.InheritFreeParameters(method); method.ResultStruct.InheritFreeParameters(method);
} }
_currentMethod = null; state.currentMethod = null;
} }
return def;
_typeNest.Pop();
} }
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.StructDataWordCount = structReader.Struct_DataWordCount;
def.StructPointerCount = structReader.Struct_PointerCount; def.StructPointerCount = structReader.Struct_PointerCount;
@ -513,12 +603,10 @@ namespace CapnpC.Model
16u * structReader.Struct_DiscriminantOffset); 16u * structReader.Struct_DiscriminantOffset);
} }
ProcessInterfaceOrStructTail(def, structReader); return ProcessInterfaceOrStructTail(def, structReader, state);
return def;
} }
TypeDefinition ProcessParameterList(Schema.Node.Reader reader, Schema.Brand.Reader brandReader, List<Field> list) TypeDefinition ProcessParameterList(Schema.Node.Reader reader, Schema.Brand.Reader brandReader, List<Field> list, Pass2State state)
{ {
//# If a named parameter list was specified in the method //# If a named parameter list was specified in the method
//# declaration (rather than a single struct parameter type) then a corresponding struct type is //# 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"); throw new InvalidSchemaException("Expected a struct");
} }
var def = ProcessTypeDef(reader.Id, state, TypeTag.Struct);
if (reader.ScopeId == 0) if (reader.ScopeId == 0)
{ {
// Auto-generated => Named parameter list // Auto-generated => Named parameter list
ProcessFields(reader, null, list); foreach (var field in def.Fields) list.Add(field);
return ProcessStruct(reader, null); return def;
} }
else else
{ {
// Single, anonymous, struct-typed parameter // Single, anonymous, struct-typed parameter
var def = GetOrCreateTypeDef(reader.Id, TypeTag.Struct);
var type = Types.FromDefinition(def); var type = Types.FromDefinition(def);
ProcessBrand(brandReader, type); ProcessBrand(brandReader, type, state);
var anon = new Field() { Type = type }; var anon = new Field() { Type = type };
list.Add(anon); list.Add(anon);
return null; 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) foreach (var superClassReader in ifaceReader.Interface_Superclasses)
{ {
var superClass = GetOrCreateTypeDef( var superClass = ProcessTypeDef(superClassReader.Id, state, TypeTag.Interface);
superClassReader.Id,
TypeTag.Interface);
def.Superclasses.Add(Types.FromDefinition(superClass)); def.Superclasses.Add(Types.FromDefinition(superClass));
} }
ProcessInterfaceOrStructTail(def, ifaceReader); return ProcessInterfaceOrStructTail(def, ifaceReader, state);
return def;
} }
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) foreach (var fieldReader in enumReader.Enumerants)
{ {
var field = new Enumerant() var field = new Enumerant()
@ -601,46 +669,52 @@ namespace CapnpC.Model
def.Enumerants.Add(field); def.Enumerants.Add(field);
} }
return def;
_typeNest.Pop();
} }
void ProcessConst(Schema.Node.Reader constReader, string name) Constant ProcessConst(Schema.Node.Reader constReader, Constant @const, Pass2State state)
{ {
var value = ProcessValue(constReader.Const_Value); var value = ProcessValue(constReader.Const_Value);
value.Type = ProcessType(constReader.Const_Type); value.Type = ProcessType(constReader.Const_Type, state);
@const.Value = value;
_typeNest.Peek().Constants.Add(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: case Annotation annotation:
break; return annotation;
case Constant constant:
case 0 when node.IsConst: def.DeclaringElement.Constants.Add(ProcessConst(node, constant, state));
ProcessConst(node, name); return def;
break; case TypeDefinition typeDef when kind == NodeKind.Enum:
return ProcessEnum(node, typeDef, state);
case 0 when node.IsEnum: case TypeDefinition typeDef when kind == NodeKind.Interface:
ProcessEnum(node, name); return ProcessInterface(node, typeDef, state);
break; case TypeDefinition typeDef when kind == NodeKind.Struct || kind == NodeKind.Group:
return ProcessStruct(node, typeDef, state);
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;
default: 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; 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();
}
} }

View File

@ -9,9 +9,16 @@ namespace CapnpC.Model
{ {
class Type: AbstractType class Type: AbstractType
{ {
// Representation of a type expression in the schema language
public TypeDefinition Definition { get; set; } public TypeDefinition Definition { get; set; }
// The model for all nodes that are not file nodes - they define types
public GenericParameter Parameter { get; set; } public GenericParameter Parameter { get; set; }
// A reference to type parameter in this scope
public Type ElementType { get; set; } public Type ElementType { get; set; }
// The type of a list element, if this is a list.
readonly Dictionary<GenericParameter, Type> _parameterBindings = readonly Dictionary<GenericParameter, Type> _parameterBindings =
new Dictionary<GenericParameter, Type>(); new Dictionary<GenericParameter, Type>();

View File

@ -2,7 +2,7 @@
using System.Linq; using System.Linq;
namespace CapnpC.Model namespace CapnpC.Model
{ {
class TypeDefinition : AbstractType, IHasNestedDefinitions, IHasGenericParameters class TypeDefinition : AbstractType, IDefinition, IHasNestedDefinitions, IHasGenericParameters
{ {
public class DiscriminationInfo public class DiscriminationInfo
{ {
@ -16,23 +16,30 @@ namespace CapnpC.Model
public uint TagOffset { get; } public uint TagOffset { get; }
} }
public TypeDefinition(TypeTag tag, ulong id) public TypeDefinition(TypeTag tag, ulong id, IHasNestedDefinitions parent)
{ {
Tag = tag; Tag = tag;
Id = id; Id = id;
DeclaringElement = parent;
if (tag == TypeTag.Group)
((TypeDefinition)parent).NestedGroups.Add(this);
else
parent.NestedDefinitions.Add(this);
} }
public ulong Id { get; } public ulong Id { get; }
public IHasNestedDefinitions DeclaringElement { get; set; } public IHasNestedDefinitions DeclaringElement { get; }
public Method UsingMethod { get; set; } public Method UsingMethod { get; set; }
public string Name { get; set; } public string Name { get; set; }
public SpecialName SpecialName { get; set; } public SpecialName SpecialName { get; set; }
public DiscriminationInfo UnionInfo { get; set; } public DiscriminationInfo UnionInfo { get; set; }
public new List<Field> Fields => base.Fields; public new List<Field> Fields => base.Fields;
public List<Enumerant> Enumerants { get; } = new List<Enumerant>(); public List<Enumerant> Enumerants { get; } = new List<Enumerant>();
public List<TypeDefinition> NestedTypes { get; } = new List<TypeDefinition>(); public ICollection<IDefinition> NestedDefinitions { get; } = new List<IDefinition>();
public IEnumerable<TypeDefinition> NestedTypes { get => this.GetNestedTypes(); }
public List<TypeDefinition> NestedGroups { get; } = new List<TypeDefinition>(); public List<TypeDefinition> NestedGroups { get; } = new List<TypeDefinition>();
public List<Value> Constants { get; } = new List<Value>(); public ICollection<Constant> Constants { get; } = new List<Constant>();
public List<Method> Methods { get; } = new List<Method>(); public List<Method> Methods { get; } = new List<Method>();
public List<Type> Superclasses { get; } = new List<Type>(); public List<Type> Superclasses { get; } = new List<Type>();
public List<string> GenericParameters { get; } = new List<string>(); public List<string> GenericParameters { get; } = new List<string>();
@ -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<GenericParameter> AllTypeParameters public IEnumerable<GenericParameter> AllTypeParameters
{ {
get get

View File

@ -1,38 +0,0 @@
using System;
using System.Collections.Generic;
namespace CapnpC.Model
{
class TypeDefinitionManager
{
readonly Dictionary<ulong, TypeDefinition> _id2def =
new Dictionary<ulong, TypeDefinition>();
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];
}
}
}

View File

@ -28,6 +28,9 @@
Group, Group,
Interface, Interface,
Enum, Enum,
AnyEnum AnyEnum,
Const,
Annotation,
File
} }
} }

View File

@ -1,6 +1,9 @@
using Capnp; using Capnp;
using System; using System;
using System.IO; using System.IO;
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleToAttribute("capnpc-csharp.tests")]
namespace CapnpC namespace CapnpC
{ {

View File

@ -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; @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 struct Node @0xe682ab4cf923a417 { # 40 bytes, 6 ptrs
id @0 :UInt64; # bits[0, 64) id @0 :UInt64; # bits[0, 64)
displayName @1 :Text; # ptr[0] displayName @1 :Text; # ptr[0]