This commit is contained in:
Christian Köllner 2019-09-10 22:48:25 +02:00
parent 107d10e3f4
commit 2f21dc217a
105 changed files with 1045 additions and 8039 deletions

View File

@ -11,11 +11,13 @@ 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}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CapnpC.CSharp.Generator.Tests", "CapnpC.CSharp.Generator.Tests\CapnpC.CSharp.Generator.Tests.csproj", "{B77AC567-E232-4072-85C3-8689566BF3D4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Capnpc.Csharp.MsBuild.Generation", "Capnpc.Csharp.MsBuild.Generation\Capnpc.Csharp.MsBuild.Generation.csproj", "{1EFC1F20-C7BB-4F44-8BF9-DBB123AACCF4}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CapnpC.CSharp.MsBuild.Generation", "CapnpC.CSharp.MsBuild.Generation\CapnpC.CSharp.MsBuild.Generation.csproj", "{1EFC1F20-C7BB-4F44-8BF9-DBB123AACCF4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CapnpC.CSharp.Generator", "CapnpC.CSharp.Generator\CapnpC.CSharp.Generator.csproj", "{C3A3BB49-356E-4762-A190-76D877BE18F7}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CapnpC.CSharp.Generator", "CapnpC.CSharp.Generator\CapnpC.CSharp.Generator.csproj", "{C3A3BB49-356E-4762-A190-76D877BE18F7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CapnpC.CSharp.MsBuild.Generation.Tests", "CapnpC.CSharp.MsBuild.Generation.Tests\CapnpC.CSharp.MsBuild.Generation.Tests.csproj", "{EF05AD68-DE31-448E-B88D-4144F928ED5D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -51,6 +53,10 @@ Global
{C3A3BB49-356E-4762-A190-76D877BE18F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C3A3BB49-356E-4762-A190-76D877BE18F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C3A3BB49-356E-4762-A190-76D877BE18F7}.Release|Any CPU.Build.0 = Release|Any CPU
{EF05AD68-DE31-448E-B88D-4144F928ED5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EF05AD68-DE31-448E-B88D-4144F928ED5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EF05AD68-DE31-448E-B88D-4144F928ED5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EF05AD68-DE31-448E-B88D-4144F928ED5D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<RootNamespace>capnpc_csharp.Tests</RootNamespace>
<RootNamespace>CapnpC.CSharp.Generator.Tests</RootNamespace>
<IsPackable>false</IsPackable>
</PropertyGroup>
@ -16,17 +16,18 @@
<PackageReference Include="SpecFlow.Tools.MsBuild.Generation" Version="3.0.225" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\capnpc-csharp\capnpc-csharp.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Remove="*.cs" />
<Compile Remove="Embedded Resources\test.cs" />
<None Remove="Embedded Resources\Empty.capnp" />
<None Remove="Embedded Resources\Empty1.capnp" />
<None Remove="Embedded Resources\invalid.capnp" />
<None Remove="Embedded Resources\null.bin" />
<None Remove="Embedded Resources\test.capnp" />
<None Remove="Embedded Resources\test.capnp.bin" />
<Compile Include="UnitTests.cs" />
<None Remove="Embedded Resources\UnitTest1.capnp" />
<Compile Include="CapnpMessageUnitTests.cs" />
<Compile Include="CodeGeneratorUnitTests.cs" />
</ItemGroup>
<ItemGroup>
@ -34,9 +35,13 @@
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Embedded Resources\Empty.capnp" />
<EmbeddedResource Include="Embedded Resources\Empty1.capnp" />
<EmbeddedResource Include="Embedded Resources\invalid.capnp" />
<EmbeddedResource Include="Embedded Resources\null.bin" />
<EmbeddedResource Include="Embedded Resources\test.capnp.bin" />
<EmbeddedResource Include="Embedded Resources\test.cs" />
<EmbeddedResource Include="Embedded Resources\UnitTest1.capnp" />
<EmbeddedResource Include="Embedded Resources\UnitTest1.capnp.bin" />
<EmbeddedResource Include="Embedded Resources\UnitTest2.capnp.bin" />
<EmbeddedResource Include="Embedded Resources\UnitTest3.capnp.bin" />
@ -47,6 +52,10 @@
<EmbeddedResource Include="Embedded Resources\schema-with-offsets.capnp.bin" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CapnpC.CSharp.Generator\CapnpC.CSharp.Generator.csproj" />
</ItemGroup>
<ItemGroup>
<SpecFlowFeatureFiles Update="CodeGenerator.feature">
<Generator>SpecFlowSingleFileGenerator</Generator>

View File

@ -0,0 +1,107 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Text;
namespace CapnpC.CSharp.Generator.Tests
{
[TestClass]
public class CapnpMessageUnitTests
{
[TestMethod]
public void ParseError()
{
var msg = new CapnpMessage(@"f:\code\invalid.capnp:5:1: error: Parse error.");
Assert.AreEqual(@"f:\code\invalid.capnp:5:1: error: Parse error.", msg.FullMessage);
Assert.IsTrue(msg.IsParseSuccess);
Assert.AreEqual(@"f:\code\invalid.capnp", msg.FileName);
Assert.AreEqual(5, msg.Line);
Assert.AreEqual(1, msg.Column);
Assert.AreEqual(0, msg.EndColumn);
Assert.AreEqual("error", msg.Category);
Assert.AreEqual("Parse error.", msg.MessageText);
}
[TestMethod]
public void ColumnSpan()
{
var msg = new CapnpMessage(@"f:\code\invalid.capnp:10:7-8: error: Duplicate ordinal number.");
Assert.IsTrue(msg.IsParseSuccess);
Assert.AreEqual(@"f:\code\invalid.capnp", msg.FileName);
Assert.AreEqual(10, msg.Line);
Assert.AreEqual(7, msg.Column);
Assert.AreEqual(8, msg.EndColumn);
Assert.AreEqual("error", msg.Category);
Assert.AreEqual("Duplicate ordinal number.", msg.MessageText);
}
[TestMethod]
public void NoSuchFile()
{
var msg = new CapnpMessage(@"C:\ProgramData\chocolatey\lib\capnproto\tools\capnproto-tools-win32-0.7.0\capnp.exe compile: doesnotexist.capnp: no such file");
Assert.IsFalse(msg.IsParseSuccess);
Assert.AreEqual(@"C:\ProgramData\chocolatey\lib\capnproto\tools\capnproto-tools-win32-0.7.0\capnp.exe compile: doesnotexist.capnp: no such file", msg.FullMessage);
}
[TestMethod]
public void NoId()
{
var msg = new CapnpMessage(@"empty.capnp:1:1: error: File does not declare an ID. I've generated one for you. Add this line to your file: @0xc82955a0c779197d;");
Assert.IsTrue(msg.IsParseSuccess);
Assert.AreEqual("empty.capnp", msg.FileName);
Assert.AreEqual(1, msg.Line);
Assert.AreEqual(1, msg.Column);
Assert.AreEqual("error", msg.Category);
Assert.AreEqual("File does not declare an ID. I've generated one for you. Add this line to your file: @0xc82955a0c779197d;", msg.MessageText);
}
[TestMethod]
public void AnnoyingNTFSAlternateDataStream1()
{
var msg = new CapnpMessage(@"3:2:1:1: error: File does not declare an ID. I've generated one for you. Add this line to your file: @0xc82955a0c779197d;");
Assert.IsTrue(msg.IsParseSuccess);
Assert.AreEqual("3:2", msg.FileName);
Assert.AreEqual(1, msg.Line);
Assert.AreEqual(1, msg.Column);
Assert.AreEqual("error", msg.Category);
Assert.AreEqual("File does not declare an ID. I've generated one for you. Add this line to your file: @0xc82955a0c779197d;", msg.MessageText);
}
[TestMethod]
public void AnnoyingNTFSAlternateDataStream2()
{
var msg = new CapnpMessage(@"c:\3:2:1:1: error: File does not declare an ID. I've generated one for you. Add this line to your file: @0xc82955a0c779197d;");
Assert.IsTrue(msg.IsParseSuccess);
Assert.AreEqual(@"c:\3:2", msg.FileName);
Assert.AreEqual(1, msg.Line);
Assert.AreEqual(1, msg.Column);
Assert.AreEqual("error", msg.Category);
Assert.AreEqual("File does not declare an ID. I've generated one for you. Add this line to your file: @0xc82955a0c779197d;", msg.MessageText);
}
[TestMethod]
public void AnnoyingNTFSAlternateDataStream3()
{
var msg = new CapnpMessage(@"\\?\c:\3:2:1:1: error: File does not declare an ID. I've generated one for you. Add this line to your file: @0xc82955a0c779197d;");
Assert.IsTrue(msg.IsParseSuccess);
Assert.AreEqual(@"\\?\c:\3:2", msg.FileName);
Assert.AreEqual(1, msg.Line);
Assert.AreEqual(1, msg.Column);
Assert.AreEqual("error", msg.Category);
Assert.AreEqual("File does not declare an ID. I've generated one for you. Add this line to your file: @0xc82955a0c779197d;", msg.MessageText);
}
[TestMethod]
public void AnnoyingNTFSAlternateDataStream4()
{
var msg = new CapnpMessage(@"1:2-3:10:7-8: error: Duplicate ordinal number.");
Assert.IsTrue(msg.IsParseSuccess);
Assert.AreEqual(@"1:2-3", msg.FileName);
Assert.AreEqual(10, msg.Line);
Assert.AreEqual(7, msg.Column);
Assert.AreEqual(8, msg.EndColumn);
Assert.AreEqual("error", msg.Category);
Assert.AreEqual("Duplicate ordinal number.", msg.MessageText);
}
}
}

View File

@ -0,0 +1,48 @@
Feature: CodeGenerator
In order to ensure that the generator backend produces valid output
As a contributor
I want to get notified when there is any deviation from reference output
Scenario: Comparing backend output with reference
Given I have a binary code generator request "test.capnp.bin"
And my reference output is "test.cs"
When I invoke capnpc-csharp
Then the generated output must match the reference
Scenario Outline: Invalid binary code generator requests
Given I have a binary code generator request <bin>
When I invoke capnpc-csharp
Then the invocation must fail
Examples:
| bin |
| null.bin |
| test.cs |
Scenario: Combining frontend and backend
Given capnp.exe is installed on my system
And I have a schema "UnitTest1.capnp"
When I try to generate code from that schema
Then code generation must succeed
Scenario: Missing frontend
Given capnp.exe is not installed on my system
And I have a schema "UnitTest1.capnp"
When I try to generate code from that schema
Then the invocation must fail
Scenario: Schema without ID
Given capnp.exe is installed on my system
And I have a schema "Empty1.capnp"
When I try to generate code from that schema
Then the invocation must fail
And the reason must be bad input
And the error output must contain "File does not declare an ID"
Scenario: Multiple errors
Given capnp.exe is installed on my system
And I have a schema "invalid.capnp"
When I try to generate code from that schema
Then the invocation must fail
And the reason must be bad input
And the error output must contain multiple messages

View File

@ -10,7 +10,7 @@
// ------------------------------------------------------------------------------
#region Designer generated code
#pragma warning disable
namespace capnpc_csharp.Tests
namespace CapnpC.CSharp.Generator.Tests
{
using TechTalk.SpecFlow;
@ -63,7 +63,7 @@ namespace capnpc_csharp.Tests
if (((testRunner.FeatureContext != null)
&& (testRunner.FeatureContext.FeatureInfo.Title != "CodeGenerator")))
{
global::capnpc_csharp.Tests.CodeGeneratorFeature.FeatureSetup(null);
global::CapnpC.CSharp.Generator.Tests.CodeGeneratorFeature.FeatureSetup(null);
}
}
@ -149,6 +149,98 @@ this.InvalidBinaryCodeGeneratorRequests("null.bin", ((string[])(null)));
this.InvalidBinaryCodeGeneratorRequests("test.cs", ((string[])(null)));
#line hidden
}
[Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute()]
[Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute("Combining frontend and backend")]
[Microsoft.VisualStudio.TestTools.UnitTesting.TestPropertyAttribute("FeatureTitle", "CodeGenerator")]
public virtual void CombiningFrontendAndBackend()
{
TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Combining frontend and backend", null, ((string[])(null)));
#line 22
this.ScenarioInitialize(scenarioInfo);
this.ScenarioStart();
#line 23
testRunner.Given("capnp.exe is installed on my system", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given ");
#line 24
testRunner.And("I have a schema \"UnitTest1.capnp\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And ");
#line 25
testRunner.When("I try to generate code from that schema", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When ");
#line 26
testRunner.Then("code generation must succeed", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then ");
#line hidden
this.ScenarioCleanup();
}
[Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute()]
[Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute("Missing frontend")]
[Microsoft.VisualStudio.TestTools.UnitTesting.TestPropertyAttribute("FeatureTitle", "CodeGenerator")]
public virtual void MissingFrontend()
{
TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Missing frontend", null, ((string[])(null)));
#line 28
this.ScenarioInitialize(scenarioInfo);
this.ScenarioStart();
#line 29
testRunner.Given("capnp.exe is not installed on my system", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given ");
#line 30
testRunner.And("I have a schema \"UnitTest1.capnp\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And ");
#line 31
testRunner.When("I try to generate code from that schema", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When ");
#line 32
testRunner.Then("the invocation must fail", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then ");
#line hidden
this.ScenarioCleanup();
}
[Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute()]
[Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute("Schema without ID")]
[Microsoft.VisualStudio.TestTools.UnitTesting.TestPropertyAttribute("FeatureTitle", "CodeGenerator")]
public virtual void SchemaWithoutID()
{
TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Schema without ID", null, ((string[])(null)));
#line 34
this.ScenarioInitialize(scenarioInfo);
this.ScenarioStart();
#line 35
testRunner.Given("capnp.exe is installed on my system", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given ");
#line 36
testRunner.And("I have a schema \"Empty1.capnp\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And ");
#line 37
testRunner.When("I try to generate code from that schema", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When ");
#line 38
testRunner.Then("the invocation must fail", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then ");
#line 39
testRunner.And("the reason must be bad input", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And ");
#line 40
testRunner.And("the error output must contain \"File does not declare an ID\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And ");
#line hidden
this.ScenarioCleanup();
}
[Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute()]
[Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute("Multiple errors")]
[Microsoft.VisualStudio.TestTools.UnitTesting.TestPropertyAttribute("FeatureTitle", "CodeGenerator")]
public virtual void MultipleErrors()
{
TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Multiple errors", null, ((string[])(null)));
#line 42
this.ScenarioInitialize(scenarioInfo);
this.ScenarioStart();
#line 43
testRunner.Given("capnp.exe is installed on my system", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given ");
#line 44
testRunner.And("I have a schema \"invalid.capnp\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And ");
#line 45
testRunner.When("I try to generate code from that schema", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When ");
#line 46
testRunner.Then("the invocation must fail", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then ");
#line 47
testRunner.And("the reason must be bad input", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And ");
#line 48
testRunner.And("the error output must contain multiple messages", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And ");
#line hidden
this.ScenarioCleanup();
}
}
}
#pragma warning restore

View File

@ -1,13 +1,13 @@
using Capnp;
using Model = CapnpC.Model;
using Generator = CapnpC.Generator;
using CodeGeneratorRequest = CapnpC.Schema.CodeGeneratorRequest;
using Model = CapnpC.CSharp.Generator.Model;
using CodeGen = CapnpC.CSharp.Generator.CodeGen;
using CodeGeneratorRequest = CapnpC.CSharp.Generator.Schema.CodeGeneratorRequest;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Linq;
namespace capnpc_csharp.Tests
namespace CapnpC.CSharp.Generator.Tests
{
[TestClass]
public class CodeGeneratorUnitTests
@ -98,13 +98,13 @@ namespace capnpc_csharp.Tests
struct Run
{
public Model.SchemaModel Model;
public Generator.CodeGenerator CodeGen;
public CodeGen.CodeGenerator CodeGen;
public Model.GenFile FirstFile;
public string Code;
}
static Generator.CodeGenerator NewGeneratorFor(Model.SchemaModel model)
=> new Generator.CodeGenerator(model, new Generator.GeneratorOptions());
static CodeGen.CodeGenerator NewGeneratorFor(Model.SchemaModel model)
=> new CodeGen.CodeGenerator(model, new CodeGen.GeneratorOptions());
Run LoadAndGenerate(string inputName, int? testNum = null)
{

View File

@ -0,0 +1 @@


View File

@ -0,0 +1 @@


View File

@ -0,0 +1,11 @@
@0xa5ac546b7bf6fbbc
enum Enumerant {
byte @0;
bit @1;
}
struct Foo {
foo @0: UInt8;
bar @0: UInt8;
}

View File

@ -0,0 +1,149 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using TechTalk.SpecFlow;
namespace CapnpC.CSharp.Generator.Tests
{
[Binding]
public class CodeGeneratorSteps
{
Stream _inputStream;
string _inputSchemaFileName;
string _inputSchema;
string _referenceOutputContent;
GenerationResult _result;
public static Stream LoadResource(string name)
{
var assembly = Assembly.GetExecutingAssembly();
string[] names = assembly.GetManifestResourceNames();
string urn = Array.Find(names, n => n.EndsWith(name, StringComparison.OrdinalIgnoreCase));
Assert.IsNotNull(urn, $"Test specification error: {name} does not exist");
return assembly.GetManifestResourceStream(urn);
}
internal static bool IsCapnpExeInstalled()
{
using (var process = Process.Start("where", "capnp.exe"))
{
if (process == null)
Assert.Fail("Unable to start 'where'");
process.WaitForExit();
return process.ExitCode == 0;
}
}
[Given(@"I have a binary code generator request ""(.*)""")]
[Given(@"I have a binary code generator request (.*)")]
public void GivenIHaveABinaryCodeGeneratorRequest(string binaryRequestFileName)
{
_inputStream = LoadResource(binaryRequestFileName);
}
[Given(@"my reference output is ""(.*)""")]
public void GivenMyReferenceOutputIs(string expectedOutputFileName)
{
using (var stream = LoadResource(expectedOutputFileName))
using (var reader = new StreamReader(stream))
{
_referenceOutputContent = reader.ReadToEnd();
}
}
[When(@"I invoke capnpc-csharp")]
public void WhenIInvokeCapnpc_Csharp()
{
using (_inputStream)
{
_result = CapnpCompilation.GenerateFromStream(_inputStream);
}
}
[Then(@"the generated output must match the reference")]
public void ThenTheGeneratedOutputMustMatchTheReference()
{
Assert.IsTrue(_result.IsSuccess, $"Tool invocation failed: {_result.Exception?.Message}");
Assert.AreEqual(_referenceOutputContent, _result.GeneratedFiles.Single().GeneratedContent);
}
[Then(@"the invocation must fail")]
public void ThenTheInvocationMustFail()
{
Assert.IsFalse(_result.IsSuccess, "Tool invocation was supposed to fail, but it didn't");
Assert.IsNotNull(_result.Exception, "Expected an exception");
}
[Given(@"capnp\.exe is installed on my system")]
public void GivenCapnp_ExeIsInstalledOnMySystem()
{
if (!IsCapnpExeInstalled())
{
Assert.Inconclusive("capnp.exe not found. Precondition of this test is not met.");
}
}
[Given(@"I have a schema ""(.*)""")]
public void GivenIHaveASchema(string capnpFileName)
{
_inputSchemaFileName = capnpFileName;
using (var stream = LoadResource(capnpFileName))
using (var reader = new StreamReader(stream))
{
_inputSchema = reader.ReadToEnd();
}
}
[When(@"I try to generate code from that schema")]
public void WhenIWantToGenerateCodeFromThatSchema()
{
string path = Path.Combine(Path.GetTempPath(), _inputSchemaFileName);
File.WriteAllText(path, _inputSchema);
_result = CapnpCompilation.InvokeCapnpAndGenerate(new string[] { path });
}
[Then(@"code generation must succeed")]
public void ThenCodeGenerationMustSucceed()
{
Assert.IsNotNull(_result, "expected generation result");
Assert.IsTrue(_result.IsSuccess, $"Tool invocation failed: {_result.Exception?.Message}");
Assert.IsTrue(_result.GeneratedFiles.Count == 1, "Expected exactly one file");
Assert.IsTrue(_result.GeneratedFiles[0].IsSuccess, $"Code generation failed: {_result.GeneratedFiles[0].Exception?.Message}");
Assert.IsFalse(string.IsNullOrEmpty(_result.GeneratedFiles[0].GeneratedContent), "Expected non-empty generated content");
}
[Given(@"capnp\.exe is not installed on my system")]
public void GivenCapnp_ExeIsNotInstalledOnMySystem()
{
if (IsCapnpExeInstalled())
{
Assert.Inconclusive("capnp.exe found. Precondition of this test is not met.");
}
}
[Then(@"the reason must be bad input")]
public void ThenTheReasonMustBeBadInput()
{
Assert.IsTrue(_result.ErrorCategory == CapnpProcessFailure.BadInput);
}
[Then(@"the error output must contain ""(.*)""")]
public void ThenTheErrorOutputMustContain(string p0)
{
Assert.IsTrue(_result.Messages.Any(m => m.FullMessage.Contains(p0)));
}
[Then(@"the error output must contain multiple messages")]
public void ThenTheErrorOutputMustContainMultipleMessages()
{
Assert.IsTrue(_result.Messages.Count >= 2);
}
}
}

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup />
</Project>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<TargetFrameworks>netstandard2.0;netcoreapp2.1</TargetFrameworks>
</PropertyGroup>
<ItemGroup>

View File

@ -1,7 +1,12 @@
using Capnp;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
[assembly: InternalsVisibleTo("CapnpC.CSharp.Generator.Tests")]
namespace CapnpC.CSharp.Generator
{
@ -23,13 +28,7 @@ namespace CapnpC.CSharp.Generator
try
{
WireFrame segments;
using (input)
{
segments = Framing.ReadSegments(input);
}
var segments = Framing.ReadSegments(input);
var dec = DeserializerState.CreateRoot(segments);
var reader = Schema.CodeGeneratorRequest.Reader.Create(dec);
var model = Model.SchemaModel.Create(reader);
@ -42,8 +41,66 @@ namespace CapnpC.CSharp.Generator
}
}
public static GenerationResult InvokeCapnpcAndGenerate()
/// <summary>
/// Invokes "capnp.exe -o-" with given additional arguments and redirects the output to the C# generator backend.
/// </summary>
/// <param name="arguments">additional command line arguments</param>
/// <returns>generation result</returns>
/// <exception cref="ArgumentNullException"><paramref name="arguments"/>is null</exception>
public static GenerationResult InvokeCapnpAndGenerate(IEnumerable<string> arguments)
{
if (arguments == null)
throw new ArgumentNullException(nameof(arguments));
using (var compiler = new Process())
{
var argList = new List<string>();
argList.Add("compile");
argList.Add($"-o-");
argList.AddRange(arguments);
compiler.StartInfo.FileName = "capnp.exe";
compiler.StartInfo.Arguments = string.Join(" ", argList);
compiler.StartInfo.UseShellExecute = false;
compiler.StartInfo.RedirectStandardOutput = true;
compiler.StartInfo.RedirectStandardError = true;
try
{
compiler.Start();
}
catch (Exception exception)
{
return new GenerationResult(exception)
{
ErrorCategory = CapnpProcessFailure.NotFound
};
}
var result = GenerateFromStream(compiler.StandardOutput.BaseStream);
var messageList = new List<CapnpMessage>();
while (!compiler.StandardError.EndOfStream)
{
messageList.Add(new CapnpMessage(compiler.StandardError.ReadLine()));
}
result.Messages = messageList;
if (!result.IsSuccess)
{
compiler.WaitForExit();
int exitCode = compiler.ExitCode;
if (exitCode == 0)
result.ErrorCategory = CapnpProcessFailure.BadOutput;
else
result.ErrorCategory = CapnpProcessFailure.BadInput;
}
return result;
}
}
}
}

View File

@ -0,0 +1,116 @@
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
[assembly: InternalsVisibleTo("CapnpC.CSharp.Generator.Tests")]
namespace CapnpC.CSharp.Generator
{
/// <summary>
/// Represents a capnp.exe output message
/// </summary>
public class CapnpMessage
{
// capnp outputs look like this:
// empty.capnp:1:1: error: File does not declare an ID. I've generated one for you. Add this line to your file: @0xc82955a0c779197d;
// f:\code\invalid.capnp:9:7-8: error: Ordinal @0 originally used here.
// Parsing them is harder than it seems because the colon may be part of the file name (as in the example above).
// And it becomes even worse! NTFS has a rarely used feature called "alternate data streams", identified by a colon:
// f:\code\somefile:stream.capnp:9:7-8: error: Ordinal @0 originally used here.
// What about a name which looks like a line number? (Hint: the 10 denotes the alternate data stream)
// f:\code\somefile:10:9:7-8: error: Ordinal @0 originally used here.
// Watching for the *last* colon as message separator does not work either, either. See first example.
// Strategy: Watch out for the *last* occurence of pattern :[line]:[column]
static readonly Regex LineColumnRegex = new Regex(@":(?<Line>\d+):(?<Column>\d+)(-(?<EndColumn>\d+))?:", RegexOptions.Compiled | RegexOptions.RightToLeft);
/// <summary>
/// Constructs an instance from given message
/// </summary>
/// <param name="fullMessage">output message (one line)</param>
public CapnpMessage(string fullMessage)
{
FullMessage = fullMessage;
var match = LineColumnRegex.Match(fullMessage);
if (match.Success)
{
IsParseSuccess = true;
FileName = fullMessage.Substring(0, match.Index);
var lineMatch = match.Groups["Line"];
if (lineMatch.Success)
{
int.TryParse(lineMatch.Value, out int value);
Line = value;
}
var columnMatch = match.Groups["Column"];
if (columnMatch.Success)
{
int.TryParse(columnMatch.Value, out int value);
Column = value;
}
var endColumnMatch = match.Groups["EndColumn"];
if (endColumnMatch.Success)
{
int.TryParse(endColumnMatch.Value, out int value);
EndColumn = value;
}
int restIndex = match.Index + match.Length;
int bodyIndex = fullMessage.IndexOf(':', restIndex);
if (bodyIndex >= 0)
{
Category = fullMessage.Substring(restIndex, bodyIndex - restIndex).Trim();
MessageText = fullMessage.Substring(bodyIndex + 1).Trim();
}
else
{
// Never observed "in the wild", just in case...
Category = string.Empty;
MessageText = fullMessage.Substring(restIndex).Trim();
}
}
}
/// <summary>
/// The original message
/// </summary>
public string FullMessage { get; }
/// <summary>
/// Whether the message could be decompsed into [filename]:[line]:[column]: [category]: [text]
/// </summary>
public bool IsParseSuccess { get; }
/// <summary>
/// Parsed file name (null iff not IsParseSuccess)
/// </summary>
public string FileName { get; }
/// <summary>
/// Parsed line (0 if not IsParseSuccess)
/// </summary>
public int Line { get; }
/// <summary>
/// Parsed column (0 if not IsParseSuccess)
/// </summary>
public int Column { get; }
/// <summary>
/// Parsed end column (0 if there is none)
/// </summary>
public int EndColumn { get; }
/// <summary>
/// Parsed category (e.g. "error", null iff not IsParseSuccess)
/// </summary>
public string Category { get; }
/// <summary>
/// Parsed message body text (0 if not IsParseSuccess)
/// </summary>
public string MessageText { get; }
}
}

View File

@ -0,0 +1,23 @@
namespace CapnpC.CSharp.Generator
{
/// <summary>
/// Why did invocation of capnpc.exe fail?
/// </summary>
public enum CapnpProcessFailure
{
/// <summary>
/// Because capnpc.exe was not found. It is probably not installed.
/// </summary>
NotFound,
/// <summary>
/// Because it exited with an error. Probably invalid .capnp file input.
/// </summary>
BadInput,
/// <summary>
/// Because it produced an apparently bad code generation request.
/// </summary>
BadOutput
}
}

View File

@ -11,7 +11,7 @@
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static SyntaxHelpers;
class CodeGenerator
internal class CodeGenerator
{
readonly SchemaModel _model;
readonly GenNames _names;

View File

@ -40,5 +40,15 @@ namespace CapnpC.CSharp.Generator
/// true iff generation was successful
/// </summary>
public bool IsSuccess => GeneratedFiles != null;
/// <summary>
/// Messages read from standard error. Valid for both failure and success (capnp might spit out some warnings).
/// </summary>
public IReadOnlyList<CapnpMessage> Messages { get; internal set; }
/// <summary>
/// Error classification (if any error)
/// </summary>
public CapnpProcessFailure ErrorCategory { get; internal set; }
}
}

View File

@ -0,0 +1,42 @@
using System;
using System.Collections;
using Microsoft.Build.Framework;
namespace CapnpC.CSharp.MsBuild.Generation.Tests
{
class BuildEngineMock : IBuildEngine
{
public bool ContinueOnError => true;
public int LineNumberOfTaskNode => 0;
public int ColumnNumberOfTaskNode => 0;
public string ProjectFileOfTaskNode => null;
public bool BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs)
{
return true;
}
public void LogCustomEvent(CustomBuildEventArgs e)
{
Console.WriteLine(e.Message);
}
public void LogErrorEvent(BuildErrorEventArgs e)
{
Console.WriteLine(e.Message);
}
public void LogMessageEvent(BuildMessageEventArgs e)
{
Console.WriteLine(e.Message);
}
public void LogWarningEvent(BuildWarningEventArgs e)
{
Console.WriteLine(e.Message);
}
}
}

View File

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="15.8.166" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="MSTest.TestAdapter" Version="1.4.0" />
<PackageReference Include="MSTest.TestFramework" Version="1.4.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CapnpC.CSharp.Generator.Tests\CapnpC.CSharp.Generator.Tests.csproj" />
<ProjectReference Include="..\CapnpC.CSharp.MsBuild.Generation\CapnpC.CSharp.MsBuild.Generation.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,51 @@
using CapnpC.CSharp.Generator.Tests;
using Microsoft.Build.Framework;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.IO;
namespace CapnpC.CSharp.MsBuild.Generation.Tests
{
[TestClass]
public class GenerateCapnpFileCodeBehindTaskTest
{
string LoadResourceContent(string name)
{
using (var stream = CodeGeneratorSteps.LoadResource("UnitTest1.capnp"))
using (var reader = new StreamReader(stream))
{
return reader.ReadToEnd();
}
}
[TestMethod]
public void ExecutionWithoutParameters()
{
var task = new GenerateCapnpFileCodeBehindTask();
task.BuildEngine = new BuildEngineMock();
task.Execute();
// Should not crash. Should Execute() return true or false if there is no input?
}
[TestMethod]
public void SimpleGeneration()
{
string capnpFile = "UnitTask1.capnp";
string content = LoadResourceContent(capnpFile);
string tmpPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
Directory.CreateDirectory(tmpPath);
string capnpPath = Path.Combine(tmpPath, capnpFile);
File.WriteAllText(capnpPath, content);
var task = new GenerateCapnpFileCodeBehindTask();
task.BuildEngine = new BuildEngineMock();
task.ProjectPath = Path.Combine(tmpPath, "doesnotneedtoexist.csproj");
task.CapnpFiles = new ITaskItem[1] { new TaskItemMock() { ItemSpec = capnpPath } };
Assert.IsTrue(task.Execute());
Assert.IsNotNull(task.GeneratedFiles);
Assert.AreEqual(1, task.GeneratedFiles.Length);
string csPath = Path.Combine(tmpPath, task.GeneratedFiles[0].ItemSpec);
Assert.AreEqual(capnpPath + ".cs", csPath);
Assert.IsTrue(File.Exists(csPath));
}
}
}

View File

@ -0,0 +1,37 @@
using System.Collections;
using Microsoft.Build.Framework;
namespace CapnpC.CSharp.MsBuild.Generation.Tests
{
class TaskItemMock : ITaskItem
{
public string ItemSpec { get; set; }
public ICollection MetadataNames => null;
public int MetadataCount => 0;
public IDictionary CloneCustomMetadata()
{
return null;
}
public void CopyMetadataTo(ITaskItem destinationItem)
{
}
public string GetMetadata(string metadataName)
{
return null;
}
public void RemoveMetadata(string metadataName)
{
}
public void SetMetadata(string metadataName, string metadataValue)
{
}
}
}

View File

@ -0,0 +1,51 @@
using CapnpC.CSharp.Generator;
using System;
using System.Collections.Generic;
using System.Linq;
namespace CapnpC.CSharp.MsBuild.Generation
{
public class CsFileGeneratorResult
{
public CsFileGeneratorResult(FileGenerationResult generatorResult, string fileName, IReadOnlyList<CapnpMessage> messages)
{
if (generatorResult == null)
{
throw new ArgumentNullException(nameof(generatorResult));
}
Filename = fileName ?? throw new ArgumentNullException(nameof(fileName));
Error = generatorResult.Exception?.Message;
GeneratedCode = generatorResult.GeneratedContent;
Messages = messages;
}
public CsFileGeneratorResult(string error)
{
Error = error;
}
public CsFileGeneratorResult(string error, IReadOnlyList<CapnpMessage> messages)
{
Error = error;
Messages = messages;
}
/// <summary>
/// The error, if any.
/// </summary>
public string Error { get; }
/// <summary>
/// The generated code.
/// </summary>
public string GeneratedCode { get; }
public IReadOnlyList<CapnpMessage> Messages { get; }
public bool Success => Error == null;
public string Filename { get; }
}
}

View File

@ -1,41 +1,85 @@
using System;
using CapnpC.CSharp.Generator;
using System;
using System.Collections.Generic;
using System.IO;
namespace Capnpc.Csharp.MsBuild.Generation
namespace CapnpC.CSharp.MsBuild.Generation
{
public class CapnpCodeBehindGenerator : IDisposable
{
//private SpecFlowProject _specFlowProject;
//private ITestGenerator _testGenerator;
public void InitializeProject(string projectPath)
{
//_specFlowProject = MsBuildProjectReader.LoadSpecFlowProjectFromMsBuild(Path.GetFullPath(projectPath), rootNamespace);
//var projectSettings = _specFlowProject.ProjectSettings;
//var testGeneratorFactory = new TestGeneratorFactory();
//_testGenerator = testGeneratorFactory.CreateGenerator(projectSettings, generatorPlugins);
}
public TestFileGeneratorResult GenerateCodeBehindFile(string capnpFile)
public CsFileGeneratorResult GenerateCodeBehindFile(string capnpFile)
{
//var featureFileInput = new FeatureFileInput(featureFile);
//var generatedFeatureFileName = Path.GetFileName(_testGenerator.GetTestFullPath(featureFileInput));
// Works around a weird capnp.exe behavior: When the input file is empty, it will spit out an exception dump
// instead of a parse error. But the parse error is nice because it contains a generated ID. We want the parse error!
// Workaround: Generate a temporary file that contains a single line break (such that it is not empty...)
try
{
if (File.Exists(capnpFile) && new FileInfo(capnpFile).Length == 0)
{
string tempFile = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".capnp");
//var testGeneratorResult = _testGenerator.GenerateTestFile(featureFileInput, new GenerationSettings());
File.WriteAllText(tempFile, Environment.NewLine);
try
{
return GenerateCodeBehindFile(tempFile);
}
finally
{
File.Delete(tempFile);
}
}
}
catch
{
}
return new TestFileGeneratorResult(
new TestGeneratorResult() { GeneratedTestCode = "//dummy" },
capnpFile + ".cs");
var result = CapnpCompilation.InvokeCapnpAndGenerate(new string[] { capnpFile });
if (result.IsSuccess)
{
if (result.GeneratedFiles.Count == 1)
{
return new CsFileGeneratorResult(
result.GeneratedFiles[0],
capnpFile + ".cs",
result.Messages);
}
else
{
return new CsFileGeneratorResult(
"Code generation produced more than one file. This is not supported.",
result.Messages);
}
}
else
{
switch (result.ErrorCategory)
{
case CapnpProcessFailure.NotFound:
return new CsFileGeneratorResult("Unable to find capnp.exe - please install capnproto on your system first.");
case CapnpProcessFailure.BadInput:
return new CsFileGeneratorResult("Invalid schema", result.Messages);
case CapnpProcessFailure.BadOutput:
return new CsFileGeneratorResult(
"Internal error: capnp.exe produced a binary code generation request which was not understood by the backend",
result.Messages);
default:
throw new NotSupportedException("Invalid error category");
}
}
}
public void Dispose()
{
//_testGenerator?.Dispose();
}
}
}

View File

@ -5,16 +5,13 @@ using System.Text;
using System.Threading.Tasks;
using Microsoft.Build.Utilities;
namespace Capnpc.Csharp.MsBuild.Generation
namespace CapnpC.CSharp.MsBuild.Generation
{
public class CapnpFileCodeBehindGenerator : ICapnpcCsharpGenerator
{
private readonly FilePathGenerator _filePathGenerator;
public CapnpFileCodeBehindGenerator(TaskLoggingHelper log)
{
Log = log ?? throw new ArgumentNullException(nameof(log));
_filePathGenerator = new FilePathGenerator();
}
public TaskLoggingHelper Log { get; }
@ -39,33 +36,44 @@ namespace Capnpc.Csharp.MsBuild.Generation
foreach (var capnpFile in capnpFiles)
{
var capnpFileItemSpec = capnpFile;
var generatorResult = capnpCodeBehindGenerator.GenerateCodeBehindFile(capnpFileItemSpec);
var generatorResult = capnpCodeBehindGenerator.GenerateCodeBehindFile(capnpFile);
if (!generatorResult.Success)
{
foreach (var error in generatorResult.Errors)
if (!string.IsNullOrEmpty(generatorResult.Error))
{
//Log.LogError(
// null,
// null,
// null,
// featureFile,
// error.Line,
// error.LinePosition,
// 0,
// 0,
// error.Message);
Log.LogError("{0}", generatorResult.Error);
}
if (generatorResult.Messages != null)
{
foreach (var message in generatorResult.Messages)
{
if (message.IsParseSuccess)
{
Log.LogError(
subcategory: null,
errorCode: null,
helpKeyword: null,
file: capnpFile,
lineNumber: message.Line,
columnNumber: message.Column,
endLineNumber: message.Line,
endColumnNumber: message.EndColumn == 0 ? message.Column : message.EndColumn,
"{0}",
message.MessageText);
}
else
{
Log.LogError("{0}", message.FullMessage);
}
}
}
continue;
}
var targetFilePath = _filePathGenerator.GenerateFilePath(
projectFolder,
capnpFile,
generatorResult.Filename);
var resultedFile = codeBehindWriter.WriteCodeBehindFile(targetFilePath, capnpFile, generatorResult);
var resultedFile = codeBehindWriter.WriteCodeBehindFile(generatorResult.Filename, generatorResult);
yield return FileSystemHelper.GetRelativePath(resultedFile, projectFolder);
}

View File

@ -2,9 +2,6 @@
<PropertyGroup>
<TargetFrameworks>net471;netcoreapp2.1</TargetFrameworks>
<SignAssembly>false</SignAssembly>
<NuspecFile>$(MSBuildThisFileDirectory)Capnpc.Csharp.MsBuild.Generation.nuspec</NuspecFile>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<NoPackageAnalysis>true</NoPackageAnalysis>
@ -12,6 +9,13 @@
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>1.0.0.0</FileVersion>
<Version>1.0.0</Version>
<NuspecFile>$(MSBuildThisFileDirectory)CapnpC.CSharp.MsBuild.Generation.nuspec</NuspecFile>
<NuspecProperties>version=$(Version)</NuspecProperties>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
@ -54,6 +58,12 @@
<Folder Include="FrameworkDependent\FullFramework\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CapnpC.CSharp.Generator\CapnpC.CSharp.Generator.csproj">
<Private>true</Private>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.Build">
<HintPath>Microsoft.Build</HintPath>
@ -68,11 +78,28 @@
</ItemGroup>
<ItemGroup>
<None Update="buildMultiTargeting\CapnpC.CSharp.MsBuild.Generation.props">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="build\CapnpC.CSharp.MsBuild.Generation.props">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="build\CapnpC.CSharp.MsBuild.Generation.targets">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="build\CapnpC.CSharp.MsBuild.Generation.tasks">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="build\CPS\Buildsystem\CpsExtension.DesignTime.targets">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="build\CPS\Buildsystem\Rules\CapnpFileType.xaml">
<Generator>MSBuild:Compile</Generator>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="build\CPS\Buildsystem\Rules\ProjectItemsSchema.xaml">
<Generator>MSBuild:Compile</Generator>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

View File

@ -1,9 +1,9 @@
<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Capnpc.Csharp.MsBuild.Generation</id>
<version>1.0.0</version>
<title>Capnpc.Csharp.MsBuild.Generation</title>
<id>CapnpC.CSharp.MsBuild.Generation</id>
<version>$version$</version>
<title>CapnpC.CSharp.MsBuild.Generation</title>
<authors>Christian Köllner and contributors</authors>
<owners>Christian Köllner</owners>
<description>Package to enable the .capnp -> .cs file generation during build time</description>
@ -15,6 +15,7 @@
<tags>capnproto csharp msbuild</tags>
<copyright>Christian Köllner and contributors</copyright>
<dependencies>
<dependency id="Capnp.Net.Runtime" version="1.0" />
</dependencies>
</metadata>
<files>

View File

@ -2,7 +2,7 @@
using System.IO;
using Microsoft.Build.Utilities;
namespace Capnpc.Csharp.MsBuild.Generation
namespace CapnpC.CSharp.MsBuild.Generation
{
public class CodeBehindWriter
{
@ -13,14 +13,8 @@ namespace Capnpc.Csharp.MsBuild.Generation
public TaskLoggingHelper Log { get; }
public string WriteCodeBehindFile(string outputPath, string capnpFile, TestFileGeneratorResult testFileGeneratorResult)
public string WriteCodeBehindFile(string outputPath, CsFileGeneratorResult testFileGeneratorResult)
{
//if (string.IsNullOrEmpty(testFileGeneratorResult.Filename))
//{
// Log?.LogWithNameTag(Log.LogError, $"{featureFile} has no generated filename");
// return null;
//}
string directoryPath = Path.GetDirectoryName(outputPath) ?? throw new InvalidOperationException();
Log?.LogWithNameTag(Log.LogMessage, directoryPath);
@ -35,11 +29,6 @@ namespace Capnpc.Csharp.MsBuild.Generation
}
else
{
if (!Directory.Exists(directoryPath))
{
Directory.CreateDirectory(directoryPath);
}
File.WriteAllText(outputPath, testFileGeneratorResult.GeneratedCode);
}

View File

@ -1,31 +0,0 @@
using System;
using System.IO;
namespace Capnpc.Csharp.MsBuild.Generation
{
public class FilePathGenerator
{
public string GenerateFilePath(string projectFolder, string capnpFileName, string generatedCodeBehindFileName)
{
if (projectFolder is null)
{
throw new ArgumentNullException(nameof(projectFolder));
}
if (capnpFileName is null)
{
throw new ArgumentNullException(nameof(capnpFileName));
}
if (generatedCodeBehindFileName is null)
{
throw new ArgumentNullException(nameof(generatedCodeBehindFileName));
}
string featureFileFullPath = Path.GetFullPath(Path.Combine(projectFolder, capnpFileName));
string featureFileDirPath = Path.GetDirectoryName(featureFileFullPath);
return Path.Combine(featureFileDirPath, generatedCodeBehindFileName);
}
}
}

View File

@ -4,7 +4,7 @@ using System.Diagnostics;
using System.IO;
using System.Text;
namespace Capnpc.Csharp.MsBuild.Generation
namespace CapnpC.CSharp.MsBuild.Generation
{
public static class FileSystemHelper
{

View File

@ -7,7 +7,7 @@ using System.Resources;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace Capnpc.Csharp.MsBuild.Generation
namespace CapnpC.CSharp.MsBuild.Generation
{
public class GenerateCapnpFileCodeBehindTask : Task
{

View File

@ -5,7 +5,7 @@ using System.Text;
using System.Threading.Tasks;
using Microsoft.Build.Utilities;
namespace Capnpc.Csharp.MsBuild.Generation
namespace CapnpC.CSharp.MsBuild.Generation
{
public static class LogExtensions
{
@ -15,7 +15,7 @@ namespace Capnpc.Csharp.MsBuild.Generation
string message,
params object[] messageArgs)
{
string fullMessage = $"[SpecFlow] {message}";
string fullMessage = $"[Cap'n Proto] {message}";
loggingMethod?.Invoke(fullMessage, messageArgs);
}
}

View File

@ -1,6 +1,6 @@
using System.Collections.Generic;
namespace Capnpc.Csharp.MsBuild.Generation
namespace CapnpC.CSharp.MsBuild.Generation
{
public interface ICapnpcCsharpGenerator
{

View File

@ -1,42 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Capnpc.Csharp.MsBuild.Generation
{
public class TestFileGeneratorResult
{
public TestFileGeneratorResult(TestGeneratorResult generatorResult, string fileName)
{
if (generatorResult == null)
{
throw new ArgumentNullException(nameof(generatorResult));
}
Filename = fileName ?? throw new ArgumentNullException(nameof(fileName));
Errors = generatorResult.Errors;
IsUpToDate = generatorResult.IsUpToDate;
GeneratedCode = generatorResult.GeneratedTestCode;
}
/// <summary>
/// The errors, if any.
/// </summary>
public IEnumerable<TestGenerationError> Errors { get; }
/// <summary>
/// The generated file was up-to-date.
/// </summary>
public bool IsUpToDate { get; }
/// <summary>
/// The generated code.
/// </summary>
public string GeneratedCode { get; }
public bool Success => Errors == null || !Errors.Any();
public string Filename { get; }
}
}

View File

@ -1,6 +0,0 @@
namespace Capnpc.Csharp.MsBuild.Generation
{
public class TestGenerationError
{
}
}

View File

@ -1,11 +0,0 @@
using System.Collections.Generic;
namespace Capnpc.Csharp.MsBuild.Generation
{
public class TestGeneratorResult
{
public IEnumerable<TestGenerationError> Errors { get; internal set; }
public bool IsUpToDate { get; internal set; }
public string GeneratedTestCode { get; internal set; }
}
}

View File

@ -29,7 +29,7 @@
<StringProperty Name="Link" Visible="false" />
<StringProperty Name="Generator" Visible="true" DisplayName="Custom Tool"/>
<StringProperty Name="WorkingDirectory" DisplayName="Working Directory" ReadOnly="false" Category="Misc">
<!--<StringProperty Name="WorkingDirectory" DisplayName="Working Directory" ReadOnly="false" Category="Misc">
<StringProperty.DataSource>
<DataSource Persistence="ProjectFile" ItemType="WorkingDirectory" PersistedName="WorkingDirectory" />
</StringProperty.DataSource>
@ -39,5 +39,5 @@
<StringProperty.DataSource>
<DataSource Persistence="ProjectFile" ItemType="AdditionalOptions" PersistedName="AdditionalOptions" />
</StringProperty.DataSource>
</StringProperty>
</StringProperty>-->
</Rule>

View File

@ -54,7 +54,7 @@
- after deletion of a feature file
- after pulling latest changes from version control with above changes
-->
<SpecFlowObsoleteCodeBehindFiles Include="**\*.capnp.cs" Exclude="@(CapnpFiles->'%(CodeBehindFile)')" />
<CapnpCsharpObsoleteCodeBehindFiles Include="**\*.capnp.cs" Exclude="@(CapnpFiles->'%(CodeBehindFile)')" />
<!-- Support for Visual Studio Incremental Build
https://github.com/techtalk/SpecFlow/issues/1319
@ -69,9 +69,9 @@
<PropertyGroup>
<_CapnpcCsharp_TaskFolder Condition=" '$(MSBuildRuntimeType)' == 'Core' And '$(_CapnpcCsharp_TaskFolder)' == ''">netcoreapp2.1</_CapnpcCsharp_TaskFolder>
<_CapnpcCsharp_TaskFolder Condition=" '$(MSBuildRuntimeType)' != 'Core' And '$(_CapnpcCsharp_TaskFolder)' == ''">net471</_CapnpcCsharp_TaskFolder>
<_CapnpcCsharp_TaskAssembly Condition=" '$(_CapnpcCsharp_TaskAssembly)' == '' ">..\tasks\$(_CapnpcCsharp_TaskFolder)\Capnpc.Csharp.MsBuild.Generation.dll</_CapnpcCsharp_TaskAssembly>
<_CapnpcCsharp_TaskAssembly Condition=" '$(_CapnpcCsharp_TaskAssembly)' == '' ">..\tasks\$(_CapnpcCsharp_TaskFolder)\CapnpC.CSharp.MsBuild.Generation.dll</_CapnpcCsharp_TaskAssembly>
</PropertyGroup>
<Import Project="Capnpc.Csharp.MsBuild.Generation.tasks"/>
<Import Project="CapnpC.CSharp.MsBuild.Generation.tasks"/>
</Project>

View File

@ -1,6 +1,6 @@
<Project>
<Import Project="Capnpc.Csharp.MsBuild.Generation.props" Condition="'$(_CapnpcCsharpPropsImported)'==''"/>
<Import Project="CapnpC.CSharp.MsBuild.Generation.props" Condition="'$(_CapnpcCsharpPropsImported)'==''"/>
<PropertyGroup Condition="'$(BuildServerMode)' == ''">
<BuildServerMode Condition="'$(BuildingInsideVisualStudio)'=='true'">false</BuildServerMode>
@ -48,9 +48,9 @@
</ItemGroup>
<Target Name="WarnForFeatureCodeBehindFilesWithoutCorrespondingFeatureFile" AfterTargets="CoreCompile"
<Target Name="WarnForCapnpCsharpCodeBehindFilesWithoutCorrespondingCapnpFile" AfterTargets="CoreCompile"
Condition="'$(CapnpcCsharp_EnableWarnForFeatureCodeBehindFilesWithoutCorrespondingCapnpFile)' == 'true'">
<Warning Text="For codebehind file '@(SpecFlowObsoleteCodeBehindFiles)', no capnp file was found." File="@(SpecFlowObsoleteCodeBehindFiles)" Condition="'@(SpecFlowObsoleteCodeBehindFiles)' != ''" />
<Warning Text="For codebehind file '@(CapnpCsharpObsoleteCodeBehindFiles)', no capnp file was found." File="@(CapnpCsharpObsoleteCodeBehindFiles)" Condition="'@(CapnpCsharpObsoleteCodeBehindFiles)' != ''" />
</Target>
@ -100,7 +100,7 @@
Condition="'%(Compile.Identity)' == '@(CapnpFiles->'%(CodeBehindFile)')'" />
<!-- remove files which got obsolete, typically after rename operation, or getting changes from source control -->
<Compile Remove="@(SpecFlowObsoleteCodeBehindFiles)" />
<Compile Remove="@(CapnpCsharpObsoleteCodeBehindFiles)" />
</ItemGroup>
</Target>
@ -128,6 +128,6 @@
- after deletion of a capnp file
- after pulling latest changes from version control with above changes
-->
<Delete Files="@(SpecFlowObsoleteCodeBehindFiles)" ContinueOnError="true" />
<Delete Files="@(CapnpCsharpObsoleteCodeBehindFiles)" ContinueOnError="true" />
</Target>
</Project>

View File

@ -1,3 +1,3 @@
<Project>
<UsingTask TaskName="Capnpc.Csharp.MsBuild.Generation.GenerateCapnpFileCodeBehindTask" AssemblyFile="$(_CapnpcCsharp_TaskAssembly)" />
<UsingTask TaskName="CapnpC.CSharp.MsBuild.Generation.GenerateCapnpFileCodeBehindTask" AssemblyFile="$(_CapnpcCsharp_TaskAssembly)" />
</Project>

View File

@ -1,5 +1,5 @@
<Project TreatAsLocalProperty="TaskFolder;TaskAssembly">
<Import Project="..\build\Capnpc.Csharp.MsBuild.Generation.props"/>
<Import Project="..\build\CapnpC.CSharp.MsBuild.Generation.props"/>
</Project>

View File

@ -53,7 +53,7 @@ Solution/project structure is as follows:
* Capnp.Net.Runtime is the runtime implementation, a .NET assembly.
* capnpc-csharp is the generator backend for C# language.
* Capnp.Net.Runtime.Tests is an MS Unit Testing assembly, containing - you guessed it - the test suite.
* capnpc-csharp.tests contains the generator backend test suite.
* CapnpC.CSharp.Generator.Tests contains the generator backend test suite.
- CapnpCompatTest.sln compiles to a native x86 executable which depends on the original Cap'n Proto C++ implementation. It is (partially) required by the test suite for interoperability testing.
## Features

View File

@ -29,7 +29,7 @@ before_build:
- cmd: dotnet restore ./Capnp.Net.Runtime.Tests/Capnp.Net.Runtime.Tests.Std20.csproj --verbosity m
- cmd: dotnet restore ./Capnp.Net.Runtime.Tests.Core21/Capnp.Net.Runtime.Tests.Core21.csproj --verbosity m
- cmd: dotnet restore ./capnpc-csharp/capnpc-csharp.csproj --verbosity m
- cmd: dotnet restore ./capnpc-csharp.tests/capnpc-csharp.tests.csproj --verbosity m
- cmd: dotnet restore ./CapnpC.CSharp.Generator.Tests/CapnpC.CSharp.Generator.Tests.csproj --verbosity m
build_script:
- cmd: msbuild ./Capnp.Net.sln /p:Configuration="Debug"
- cmd: msbuild ./Capnp.Net.sln /p:Configuration="Release"
@ -50,11 +50,12 @@ artifacts:
type: NuGetPackage
clone_depth: 1
test_script:
- cmd: vstest.console /logger:Appveyor /inIsolation capnpc-csharp.tests\bin\Release\netcoreapp2.2\capnpc-csharp.tests.dll
- cmd: vstest.console /logger:Appveyor /inIsolation CapnpC.CSharp.Generator.Tests\bin\Release\netcoreapp2.2\CapnpC.CSharp.Generator.Tests.dll
- cmd: cd %APPVEYOR_BUILD_FOLDER%\chocolatey\install
- cmd: choco install capnpc-csharp --source=".;https://chocolatey.org/api/v2" --force -y
- cmd: cd %APPVEYOR_BUILD_FOLDER%\install-test
- cmd: compile-test
- cmd: vstest.console /logger:Appveyor /inIsolation CapnpC.CSharp.Generator.Tests\bin\Release\netcoreapp2.2\CapnpC.CSharp.Generator.Tests.dll
- cmd: choco uninstall capnpc-csharp -y
- cmd: notinstalled-test
- cmd: cd %APPVEYOR_BUILD_FOLDER%\chocolatey\install

View File

@ -1,20 +0,0 @@
Feature: CodeGenerator
In order to ensure that the generator backend produces valid output
As a contributor
I want to get notified when there is any deviation from reference output
Scenario: Comparing backend output with reference
Given I have a binary code generator request "test.capnp.bin"
And my reference output is "test.cs"
When I invoke capnpc-csharp
Then the generated output must match the reference
Scenario Outline: Invalid binary code generator requests
Given I have a binary code generator request <bin>
When I invoke capnpc-csharp
Then the invocation must fail
Examples:
| bin |
| null.bin |
| test.cs |

View File

@ -1,83 +0,0 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.IO;
using System.Reflection;
using TechTalk.SpecFlow;
namespace capnpc_csharp.Tests
{
[Binding]
public class CodeGeneratorSteps
{
Stream _inputStream;
string _referenceOutputContent;
string _exceptedOutputFileName;
string _actualGeneratedContent;
bool _success;
Exception _generateException;
internal static Stream LoadResource(string name)
{
var assembly = Assembly.GetExecutingAssembly();
string[] names = assembly.GetManifestResourceNames();
string urn = Array.Find(names, n => n.EndsWith(name, StringComparison.OrdinalIgnoreCase));
Assert.IsNotNull(urn, $"Test specification error: {name} does not exist");
return assembly.GetManifestResourceStream(urn);
}
[Given(@"I have a binary code generator request ""(.*)""")]
[Given(@"I have a binary code generator request (.*)")]
public void GivenIHaveABinaryCodeGeneratorRequest(string binaryRequestFileName)
{
_inputStream = LoadResource(binaryRequestFileName);
}
[Given(@"my reference output is ""(.*)""")]
public void GivenMyReferenceOutputIs(string expectedOutputFileName)
{
_exceptedOutputFileName = expectedOutputFileName;
using (var stream = LoadResource(expectedOutputFileName))
using (var reader = new StreamReader(stream))
{
_referenceOutputContent = reader.ReadToEnd();
}
}
[When(@"I invoke capnpc-csharp")]
public void WhenIInvokeCapnpc_Csharp()
{
try
{
using (_inputStream)
{
string tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
Directory.CreateDirectory(tempDir);
Environment.CurrentDirectory = tempDir;
CapnpC.Program.GenerateFromStream(_inputStream);
string outPath = Path.Combine(tempDir, _exceptedOutputFileName);
_actualGeneratedContent = File.ReadAllText(outPath);
_success = true;
}
}
catch (Exception exception)
{
_generateException = exception;
}
}
[Then(@"the generated output must match the reference")]
public void ThenTheGeneratedOutputMustMatchTheReference()
{
Assert.IsTrue(_success, $"Code generation failed: {_generateException?.Message}");
Assert.AreEqual(_referenceOutputContent, _actualGeneratedContent);
}
[Then(@"the invocation must fail")]
public void ThenTheInvocationMustFail()
{
Assert.IsFalse(_success, "Code generation was supposed to fail, but it didn't");
}
}
}

View File

@ -1,182 +0,0 @@
namespace CapnpC.Generator
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using CapnpC.Model;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static SyntaxHelpers;
class CodeGenerator
{
readonly SchemaModel _model;
readonly GenNames _names;
readonly CommonSnippetGen _commonGen;
readonly DomainClassSnippetGen _domClassGen;
readonly ReaderSnippetGen _readerGen;
readonly WriterSnippetGen _writerGen;
readonly InterfaceSnippetGen _interfaceGen;
public CodeGenerator(SchemaModel model, GeneratorOptions options)
{
_model = model;
_names = new GenNames(options);
_commonGen = new CommonSnippetGen(_names);
_domClassGen = new DomainClassSnippetGen(_names);
_readerGen = new ReaderSnippetGen(_names);
_writerGen = new WriterSnippetGen(_names);
_interfaceGen = new InterfaceSnippetGen(_names);
}
internal GenNames GetNames() => _names;
IEnumerable<MemberDeclarationSyntax> TransformEnum(TypeDefinition def)
{
yield return _commonGen.MakeEnum(def);
}
IEnumerable<TypeParameterSyntax> MakeTypeParameters(TypeDefinition def)
{
foreach (string name in def.GenericParameters)
{
yield return TypeParameter(_names.GetGenericTypeParameter(name).Identifier);
}
}
IEnumerable<TypeParameterConstraintClauseSyntax> MakeTypeParameterConstraints(TypeDefinition def)
{
foreach (string name in def.GenericParameters)
{
yield return TypeParameterConstraintClause(
_names.GetGenericTypeParameter(name).IdentifierName)
.AddConstraints(ClassOrStructConstraint(SyntaxKind.ClassConstraint));
}
}
IEnumerable<MemberDeclarationSyntax> TransformStruct(TypeDefinition def)
{
var topDecl = ClassDeclaration(_names.MakeTypeName(def).Identifier)
.AddModifiers(Public)
.AddBaseListTypes(SimpleBaseType(Type<Capnp.ICapnpSerializable>()));
if (def.GenericParameters.Count > 0)
{
topDecl = topDecl
.AddTypeParameterListParameters(MakeTypeParameters(def).ToArray())
.AddConstraintClauses(MakeTypeParameterConstraints(def).ToArray());
}
if (def.UnionInfo != null)
{
topDecl = topDecl.AddMembers(_commonGen.MakeUnionSelectorEnum(def));
}
topDecl = topDecl.AddMembers(_domClassGen.MakeDomainClassMembers(def));
topDecl = topDecl.AddMembers(_readerGen.MakeReaderStruct(def));
topDecl = topDecl.AddMembers(_writerGen.MakeWriterStruct(def));
foreach (var nestedGroup in def.NestedGroups)
{
topDecl = topDecl.AddMembers(Transform(nestedGroup).ToArray());
}
foreach (var nestedDef in def.NestedTypes)
{
topDecl = topDecl.AddMembers(Transform(nestedDef).ToArray());
}
yield return topDecl;
}
IEnumerable<MemberDeclarationSyntax> TransformInterface(TypeDefinition def)
{
yield return _interfaceGen.MakeInterface(def);
yield return _interfaceGen.MakeProxy(def);
yield return _interfaceGen.MakeSkeleton(def);
if (_interfaceGen.RequiresPipeliningSupport(def))
{
yield return _interfaceGen.MakePipeliningSupport(def);
}
if (def.NestedTypes.Any())
{
var ns = ClassDeclaration(
_names.MakeTypeName(def, NameUsage.Namespace).ToString())
.AddModifiers(Public, Static);
if (def.GenericParameters.Count > 0)
{
ns = ns
.AddTypeParameterListParameters(MakeTypeParameters(def).ToArray())
.AddConstraintClauses(MakeTypeParameterConstraints(def).ToArray());
}
foreach (var nestedDef in def.NestedTypes)
{
ns = ns.AddMembers(Transform(nestedDef).ToArray());
}
yield return ns;
}
}
IEnumerable<MemberDeclarationSyntax> Transform(TypeDefinition def)
{
switch (def.Tag)
{
case TypeTag.Enum:
return TransformEnum(def);
case TypeTag.Group:
case TypeTag.Struct:
return TransformStruct(def);
case TypeTag.Interface:
return TransformInterface(def);
default:
throw new NotSupportedException($"Cannot declare type of kind {def.Tag} here");
}
}
internal string Transform(GenFile file)
{
NameSyntax topNamespace = GenNames.NamespaceName(file.Namespace) ?? _names.TopNamespace;
var ns = NamespaceDeclaration(topNamespace);
foreach (var def in file.NestedTypes)
{
ns = ns.AddMembers(Transform(def).ToArray());
}
var cu = CompilationUnit().AddUsings(
UsingDirective(ParseName("Capnp")),
UsingDirective(ParseName("Capnp.Rpc")),
UsingDirective(ParseName("System")),
UsingDirective(ParseName("System.Collections.Generic")),
UsingDirective(ParseName("System.Threading")),
UsingDirective(ParseName("System.Threading.Tasks")));
cu = cu.AddMembers(ns);
return cu.NormalizeWhitespace().ToFullString();
}
public void Generate()
{
foreach (var file in _model.FilesToGenerate)
{
string content = Transform(file);
string path = Path.ChangeExtension(file.Name, ".cs");
File.WriteAllText(path, content);
}
}
}
}

View File

@ -1,93 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CapnpC.Model;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static CapnpC.Generator.SyntaxHelpers;
namespace CapnpC.Generator
{
class CommonSnippetGen
{
readonly GenNames _names;
public CommonSnippetGen(GenNames names)
{
_names = names;
}
public EnumDeclarationSyntax MakeUnionSelectorEnum(TypeDefinition def)
{
var whichEnum = EnumDeclaration(_names.UnionDiscriminatorEnum.ToString())
.AddModifiers(Public)
.AddBaseListTypes(SimpleBaseType(Type<ushort>()));
var discFields = def.Fields.Where(f => f.DiscValue.HasValue);
foreach (var discField in discFields)
{
whichEnum = whichEnum.AddMembers(
EnumMemberDeclaration(_names.GetCodeIdentifier(discField).Identifier)
.WithEqualsValue(
EqualsValueClause(LiteralExpression(
SyntaxKind.NumericLiteralExpression,
Literal(discField.DiscValue.Value)))));
}
var ndecl = EnumMemberDeclaration(_names.UnionDiscriminatorUndefined.ToString()).WithEqualsValue(
EqualsValueClause(
LiteralExpression(
SyntaxKind.NumericLiteralExpression,
Literal(Schema.Field.Reader.NoDiscriminant))));
whichEnum = whichEnum.AddMembers(ndecl);
return whichEnum;
}
public EnumDeclarationSyntax MakeEnum(TypeDefinition def)
{
var decl = EnumDeclaration(def.Name)
.AddModifiers(Public)
.AddBaseListTypes(SimpleBaseType(Type<ushort>()));
foreach (var enumerant in def.Enumerants.OrderBy(e => e.CodeOrder))
{
var mdecl = EnumMemberDeclaration(enumerant.Literal);
if (enumerant.Ordinal.HasValue)
{
mdecl = mdecl.WithEqualsValue(
EqualsValueClause(
LiteralExpression(
SyntaxKind.NumericLiteralExpression,
Literal(enumerant.Ordinal.Value))));
}
decl = decl.AddMembers(mdecl);
}
return decl;
}
public static IEnumerable<SyntaxNodeOrToken> MakeCommaSeparatedList(IEnumerable<ExpressionSyntax> expressions)
{
bool first = true;
foreach (var expr in expressions)
{
if (first)
first = false;
else
yield return Token(SyntaxKind.CommaToken);
yield return expr;
}
}
}
}

View File

@ -1,972 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CapnpC.Model;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static CapnpC.Generator.SyntaxHelpers;
namespace CapnpC.Generator
{
class DomainClassSnippetGen
{
readonly GenNames _names;
public DomainClassSnippetGen(GenNames names)
{
_names = names;
}
MemberDeclarationSyntax MakeUnionField(Field field)
{
var type = _names.MakeTypeSyntax(field.Type, field.DeclaringType, TypeUsage.DomainClassNullable);
switch (field.Type.Tag)
{
case TypeTag.Void:
return null;
default:
return PropertyDeclaration(type,
_names.GetCodeIdentifier(field).Identifier)
.AddModifiers(Public).AddAccessorListAccessors(
AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
.WithExpressionBody(
ArrowExpressionClause(
ConditionalExpression(
BinaryExpression(
SyntaxKind.EqualsExpression,
_names.UnionDiscriminatorField.IdentifierName,
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
_names.UnionDiscriminatorEnum.IdentifierName,
_names.GetCodeIdentifier(field).IdentifierName)),
CastExpression(type,
_names.UnionContentField.IdentifierName),
LiteralExpression(
SyntaxKind.NullLiteralExpression))))
.WithSemicolonToken(
Token(SyntaxKind.SemicolonToken)),
AccessorDeclaration(
SyntaxKind.SetAccessorDeclaration)
.WithBody(
Block(
ExpressionStatement(
AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
_names.UnionDiscriminatorField.IdentifierName,
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
_names.UnionDiscriminatorEnum.IdentifierName,
_names.GetCodeIdentifier(field).IdentifierName))),
ExpressionStatement(
AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
_names.UnionContentField.IdentifierName,
IdentifierName("value"))))));
}
}
MemberDeclarationSyntax MakeStructField(Field field)
{
if (field.Type.Tag == TypeTag.Void)
{
return null;
}
var prop = PropertyDeclaration(_names.MakeTypeSyntax(field.Type, field.DeclaringType, TypeUsage.DomainClass),
_names.GetCodeIdentifier(field).Identifier)
.AddModifiers(Public).AddAccessorListAccessors(
AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken)),
AccessorDeclaration(SyntaxKind.SetAccessorDeclaration)
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken)));
if (field.DefaultValueIsExplicit && field.Type.IsValueType)
{
prop = prop.WithInitializer(
EqualsValueClause(MakeDefaultValue(field)))
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken));
}
return prop;
}
MemberDeclarationSyntax MakeUnionDiscriminatorField()
{
return FieldDeclaration(
VariableDeclaration(_names.UnionDiscriminatorEnum.IdentifierName)
.AddVariables(
VariableDeclarator(_names.UnionDiscriminatorField.Identifier)
.WithInitializer(
EqualsValueClause(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
_names.UnionDiscriminatorEnum.IdentifierName,
_names.UnionDiscriminatorUndefined.IdentifierName)))))
.AddModifiers(Private);
}
MemberDeclarationSyntax MakeUnionContentField()
{
return FieldDeclaration(
VariableDeclaration(SyntaxHelpers.Type<object>())
.WithVariables(
SingletonSeparatedList<VariableDeclaratorSyntax>(
VariableDeclarator(_names.UnionContentField.Identifier))))
.AddModifiers(Private);
}
IEnumerable<ExpressionSyntax> MakeInitializerAssignments(Value structValue, TypeDefinition scope)
{
foreach (var fieldValue in structValue.Fields)
{
var valueExpr = MakeValue(fieldValue.Item2, scope);
if (valueExpr == null)
continue;
yield return AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
_names.GetCodeIdentifier(fieldValue.Item1).IdentifierName,
valueExpr);
}
}
ExpressionSyntax MakeValue(Value value, TypeDefinition scope)
{
switch (value.Type.Tag)
{
case TypeTag.AnyEnum:
return LiteralExpression(
SyntaxKind.NumericLiteralExpression, Literal((ushort)value.ScalarValue));
case TypeTag.Bool:
if ((bool)value.ScalarValue)
return LiteralExpression(SyntaxKind.TrueLiteralExpression);
else
return LiteralExpression(SyntaxKind.FalseLiteralExpression);
case TypeTag.Data:
return ArrayCreationExpression(ArrayType(
PredefinedType(Token(SyntaxKind.ByteKeyword)))
.WithRankSpecifiers(
SingletonList<ArrayRankSpecifierSyntax>(
ArrayRankSpecifier(
SingletonSeparatedList<ExpressionSyntax>(
OmittedArraySizeExpression())))))
.WithInitializer(
InitializerExpression(
SyntaxKind.ArrayInitializerExpression)
.AddExpressions(value.Items.Select(v => MakeValue(v, scope)).ToArray()));
case TypeTag.Enum:
return MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
_names.MakeTypeSyntax(value.Type, scope, TypeUsage.NotRelevant),
IdentifierName(value.GetEnumerant().Literal));
case TypeTag.F32:
switch ((float)value.ScalarValue)
{
case float.NaN:
return MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
IdentifierName("float"),
IdentifierName(nameof(float.NaN)));
case float.NegativeInfinity:
return MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
IdentifierName("float"),
IdentifierName(nameof(float.NegativeInfinity)));
case float.PositiveInfinity:
return MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
IdentifierName("float"),
IdentifierName(nameof(float.PositiveInfinity)));
default:
return LiteralExpression(SyntaxKind.NumericLiteralExpression,
Literal((float)value.ScalarValue));
}
case TypeTag.F64:
switch ((double)value.ScalarValue)
{
case double.NaN:
return MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
IdentifierName("double"),
IdentifierName(nameof(double.NaN)));
case double.NegativeInfinity:
return MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
IdentifierName("double"),
IdentifierName(nameof(double.NegativeInfinity)));
case double.PositiveInfinity:
return MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
IdentifierName("double"),
IdentifierName(nameof(double.PositiveInfinity)));
default:
return LiteralExpression(SyntaxKind.NumericLiteralExpression,
Literal((double)value.ScalarValue));
}
case TypeTag.S8:
return LiteralExpression(SyntaxKind.NumericLiteralExpression,
Literal((sbyte)value.ScalarValue));
case TypeTag.S16:
return LiteralExpression(SyntaxKind.NumericLiteralExpression,
Literal((short)value.ScalarValue));
case TypeTag.S32:
return LiteralExpression(SyntaxKind.NumericLiteralExpression,
Literal((int)value.ScalarValue));
case TypeTag.S64:
return LiteralExpression(SyntaxKind.NumericLiteralExpression,
Literal((long)value.ScalarValue));
case TypeTag.U8:
return LiteralExpression(SyntaxKind.NumericLiteralExpression,
Literal((byte)value.ScalarValue));
case TypeTag.U16:
return LiteralExpression(SyntaxKind.NumericLiteralExpression,
Literal((ushort)value.ScalarValue));
case TypeTag.U32:
return LiteralExpression(SyntaxKind.NumericLiteralExpression,
Literal((uint)value.ScalarValue));
case TypeTag.U64:
return LiteralExpression(SyntaxKind.NumericLiteralExpression,
Literal((ulong)value.ScalarValue));
case TypeTag.Text:
value.Decode();
return value.ScalarValue == null ?
LiteralExpression(SyntaxKind.NullLiteralExpression) :
LiteralExpression(SyntaxKind.StringLiteralExpression,
Literal((string)value.ScalarValue));
case TypeTag.Group:
case TypeTag.Struct:
value.Decode();
return ObjectCreationExpression(
_names.MakeTypeSyntax(value.Type, scope, TypeUsage.DomainClass))
.WithArgumentList(ArgumentList())
.WithInitializer(
InitializerExpression(
SyntaxKind.ObjectInitializerExpression)
.AddExpressions(MakeInitializerAssignments(value, scope).ToArray()));
case TypeTag.ListPointer:
// TBD
return LiteralExpression(SyntaxKind.NullLiteralExpression);
case TypeTag.List when value.Type.ElementType.Tag == TypeTag.Void:
value.Decode();
return LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal((int)value.VoidListCount));
case TypeTag.List:
value.Decode();
return ArrayCreationExpression(ArrayType(
_names.MakeTypeSyntax(value.Type.ElementType, scope, TypeUsage.DomainClass))
.WithRankSpecifiers(
SingletonList<ArrayRankSpecifierSyntax>(
ArrayRankSpecifier(
SingletonSeparatedList<ExpressionSyntax>(
OmittedArraySizeExpression())))))
.WithInitializer(
InitializerExpression(
SyntaxKind.ArrayInitializerExpression)
.AddExpressions(value.Items.Select(v => MakeValue(v, scope)).ToArray()));
case TypeTag.AnyPointer:
case TypeTag.CapabilityPointer:
// TBD
return null;
case TypeTag.Interface:
return null;
default:
throw new NotImplementedException();
}
}
ExpressionSyntax MakeDefaultValue(Field field)
{
if (field.DefaultValueIsExplicit)
{
return MakeValue(field.DefaultValue, field.DeclaringType);
}
else
{
switch (field.Type.Tag)
{
case TypeTag.AnyEnum:
case TypeTag.S16:
case TypeTag.S32:
case TypeTag.S64:
case TypeTag.S8:
case TypeTag.U16:
case TypeTag.U32:
case TypeTag.U64:
case TypeTag.U8:
return LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(0));
case TypeTag.AnyPointer:
case TypeTag.CapabilityPointer:
case TypeTag.Data:
case TypeTag.Group:
case TypeTag.Interface:
case TypeTag.List:
case TypeTag.ListPointer:
case TypeTag.Struct:
case TypeTag.StructPointer:
case TypeTag.Text:
return LiteralExpression(SyntaxKind.NullLiteralExpression);
case TypeTag.Bool:
return LiteralExpression(SyntaxKind.FalseLiteralExpression);
case TypeTag.Enum:
return MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
_names.MakeTypeSyntax(field.Type, field.DeclaringType, TypeUsage.NotRelevant),
_names.UnionDiscriminatorUndefined.IdentifierName);
case TypeTag.F32:
return LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(0.0f));
case TypeTag.F64:
return LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(0.0));
default:
throw new NotImplementedException();
}
}
}
IEnumerable<SwitchSectionSyntax> MakeUnionDiscriminatorSetter(TypeDefinition def)
{
var unionFields = def.Fields.Where(f => f.DiscValue.HasValue);
foreach (var unionField in unionFields)
{
var section = SwitchSection()
.WithLabels(
SingletonList<SwitchLabelSyntax>(
CaseSwitchLabel(MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
_names.UnionDiscriminatorEnum.IdentifierName,
_names.GetCodeIdentifier(unionField).IdentifierName))));
if (unionField.Type.Tag != TypeTag.Void)
{
section = section.AddStatements(
ExpressionStatement(
AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
_names.UnionContentField.IdentifierName,
MakeDefaultValue(unionField))));
}
section = section.AddStatements(BreakStatement());
yield return section;
}
}
MemberDeclarationSyntax MakeUnionDiscriminatorProperty(TypeDefinition def)
{
return PropertyDeclaration(_names.UnionDiscriminatorEnum.IdentifierName,
_names.UnionDiscriminatorProp.Identifier)
.AddModifiers(Public).AddAccessorListAccessors(
AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
.WithExpressionBody(
ArrowExpressionClause(_names.UnionDiscriminatorField.IdentifierName))
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken)),
AccessorDeclaration(SyntaxKind.SetAccessorDeclaration)
.WithBody(
Block(
IfStatement(
BinaryExpression(
SyntaxKind.EqualsExpression,
IdentifierName("value"),
_names.UnionDiscriminatorField.IdentifierName),
ReturnStatement()),
ExpressionStatement(
AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
_names.UnionDiscriminatorField.IdentifierName,
IdentifierName("value"))),
SwitchStatement(IdentifierName("value"))
.WithOpenParenToken(
Token(SyntaxKind.OpenParenToken))
.WithCloseParenToken(
Token(SyntaxKind.CloseParenToken))
.AddSections(MakeUnionDiscriminatorSetter(def).ToArray()))));
}
MemberDeclarationSyntax MakeField(Field field)
{
if (field.DiscValue.HasValue)
return MakeUnionField(field);
else
return MakeStructField(field);
}
ExpressionSyntax MakeListSerializeParticle(Model.Type type, ExpressionSyntax writer, ExpressionSyntax domain)
{
string s = $"_s{type.GetRank().Item1}";
string v = $"_v{type.GetRank().Item1}";
switch (type.ElementType?.Tag)
{
case TypeTag.List:
case TypeTag.ListPointer:
case TypeTag.Struct:
case TypeTag.Group:
case TypeTag.StructPointer:
case TypeTag.Data:
return InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
writer,
IdentifierName(nameof(Capnp.ListOfPrimitivesSerializer<int>.Init))))
.AddArgumentListArguments(
Argument(domain),
Argument(
ParenthesizedLambdaExpression(
MakeComplexSerializeParticle(
type.ElementType,
IdentifierName(s),
IdentifierName(v)))
.AddParameterListParameters(
Parameter(Identifier(s)),
Parameter(Identifier(v)))));
default:
return InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
writer,
IdentifierName(nameof(Capnp.ListOfPrimitivesSerializer<int>.Init))))
.AddArgumentListArguments(Argument(domain));
}
}
ExpressionSyntax MakeComplexSerializeParticle(Model.Type type, ExpressionSyntax writer, ExpressionSyntax domain)
{
switch (type.Tag)
{
case TypeTag.Data:
case TypeTag.List:
return MakeListSerializeParticle(type, writer, domain);
case TypeTag.Struct:
case TypeTag.Group:
return ConditionalAccessExpression(domain,
InvocationExpression(MemberBindingExpression(_names.SerializeMethod.IdentifierName))
.AddArgumentListArguments(Argument(writer)));
default:
throw new NotImplementedException();
}
}
StatementSyntax MakeSerializeMethodFieldAssignment(Field field)
{
var writerProp = MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
_names.WriterParameter.IdentifierName,
_names.GetCodeIdentifier(field).IdentifierName);
switch (field.Type.Tag)
{
case TypeTag.Bool:
case TypeTag.Enum:
case TypeTag.F32:
case TypeTag.F64:
case TypeTag.S16:
case TypeTag.S32:
case TypeTag.S64:
case TypeTag.S8:
case TypeTag.U16:
case TypeTag.U32:
case TypeTag.U64:
case TypeTag.U8:
case TypeTag.AnyEnum:
case TypeTag.List when field.Type.Tag == TypeTag.Void:
if (field.DiscValue.HasValue)
{
return ExpressionStatement(
AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
writerProp,
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
_names.GetCodeIdentifier(field).IdentifierName,
IdentifierName(nameof(Nullable<int>.Value)))));
}
else
{
return ExpressionStatement(
AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
writerProp,
_names.GetCodeIdentifier(field).IdentifierName));
}
case TypeTag.AnyPointer:
case TypeTag.ListPointer:
case TypeTag.StructPointer:
return ExpressionStatement(
InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
_names.WriterParameter.IdentifierName,
_names.GetCodeIdentifier(field).IdentifierName),
IdentifierName(nameof(Capnp.DynamicSerializerState.SetObject))))
.AddArgumentListArguments(
Argument(_names.GetCodeIdentifier(field).IdentifierName)));
case TypeTag.CapabilityPointer:
case TypeTag.Interface:
return ExpressionStatement(
AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
writerProp,
_names.GetCodeIdentifier(field).IdentifierName));
case TypeTag.Text:
return ExpressionStatement(
AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
writerProp,
_names.GetCodeIdentifier(field).IdentifierName));
case TypeTag.Data:
case TypeTag.List:
case TypeTag.Struct:
case TypeTag.Group:
return ExpressionStatement(
MakeComplexSerializeParticle(
field.Type,
writerProp,
_names.GetCodeIdentifier(field).IdentifierName));
case TypeTag.Void:
return null;
default:
throw new NotImplementedException();
}
}
StatementSyntax MakeApplyDefaultsMethodFieldAssignment(Field field)
{
var lhs = _names.GetCodeIdentifier(field).IdentifierName;
var rhs = MakeDefaultValue(field);
if (rhs == null)
{
return null;
}
return ExpressionStatement(
AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
lhs,
BinaryExpression(SyntaxKind.CoalesceExpression,
lhs, rhs)));
}
ExpressionSyntax MakeInnerStructListConversion(ExpressionSyntax context, TypeSyntax elementType)
{
return InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
context,
IdentifierName(nameof(Capnp.ReadOnlyListExtensions.ToReadOnlyList))))
.AddArgumentListArguments(Argument(
SimpleLambdaExpression(Parameter(Identifier("_")),
InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
IdentifierName(nameof(Capnp.CapnpSerializable)),
GenericName(nameof(Capnp.CapnpSerializable.Create))
.AddTypeArgumentListArguments(elementType)))
.AddArgumentListArguments(Argument(IdentifierName("_"))))));
}
ExpressionSyntax MakeStructListConversion(ExpressionSyntax context, TypeSyntax elementType, int rank)
{
if (rank == 1)
{
return MakeInnerStructListConversion(context, elementType);
}
string lambdaVarName = $"_{rank}";
return InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
context,
IdentifierName(nameof(Capnp.ReadOnlyListExtensions.ToReadOnlyList))))
.AddArgumentListArguments(Argument(
SimpleLambdaExpression(
Parameter(Identifier(lambdaVarName)),
MakeStructListConversion(IdentifierName(lambdaVarName), elementType, rank - 1))));
}
ExpressionSyntax MakeAnyListConversion(ExpressionSyntax context)
{
return InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
context,
IdentifierName(nameof(Capnp.ReadOnlyListExtensions.ToReadOnlyList))))
.AddArgumentListArguments(Argument(
SimpleLambdaExpression(
Parameter(Identifier("_")),
CastExpression(Type<object>(), IdentifierName("_")))));
}
ExpressionSyntax MakeDeserializeMethodRightHandSide(Field field)
{
switch (field.Type.Tag)
{
case TypeTag.Struct:
case TypeTag.Group:
case TypeTag.StructPointer:
case TypeTag.AnyPointer:
return InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
IdentifierName(nameof(Capnp.CapnpSerializable)),
GenericName(nameof(Capnp.CapnpSerializable.Create))
.AddTypeArgumentListArguments(
_names.MakeTypeSyntax(
field.Type,
field.DeclaringType,
TypeUsage.DomainClass))))
.AddArgumentListArguments(Argument(MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
_names.ReaderParameter.IdentifierName,
_names.GetCodeIdentifier(field).IdentifierName)));
case TypeTag.Void:
return null;
case TypeTag.List:
(var rank, var elementType) = field.Type.GetRank();
if (elementType.Tag != TypeTag.Struct)
break;
return MakeStructListConversion(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
_names.ReaderParameter.IdentifierName,
_names.GetCodeIdentifier(field).IdentifierName),
_names.MakeTypeSyntax(elementType, field.DeclaringType, TypeUsage.DomainClass),
rank);
case TypeTag.ListPointer:
return MakeAnyListConversion(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
_names.ReaderParameter.IdentifierName,
_names.GetCodeIdentifier(field).IdentifierName));
}
return MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
_names.ReaderParameter.IdentifierName,
_names.GetCodeIdentifier(field).IdentifierName);
}
IEnumerable<SwitchSectionSyntax> MakeSerializeMethodSwitchSections(TypeDefinition def)
{
var unionFields = def.Fields.Where(f => f.DiscValue.HasValue);
foreach (var unionField in unionFields)
{
var section = SwitchSection()
.AddLabels(
CaseSwitchLabel(MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
_names.UnionDiscriminatorEnum.IdentifierName,
_names.GetCodeIdentifier(unionField).IdentifierName)));
if (unionField.Type.Tag != TypeTag.Void)
{
ExpressionSyntax right = _names.GetCodeIdentifier(unionField).IdentifierName;
var syntax = _names.MakeTypeSyntax(unionField.Type, unionField.DeclaringType, TypeUsage.DomainClassNullable);
if (syntax is NullableTypeSyntax)
{
right = MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
right,
IdentifierName(nameof(Nullable<int>.Value)));
}
section = section.AddStatements(MakeSerializeMethodFieldAssignment(unionField));
}
section = section.AddStatements(BreakStatement());
yield return section;
}
}
IEnumerable<SwitchSectionSyntax> MakeDeserializeMethodSwitchSections(TypeDefinition def)
{
var unionFields = def.Fields.Where(f => f.DiscValue.HasValue);
foreach (var unionField in unionFields)
{
var section = SwitchSection()
.AddLabels(
CaseSwitchLabel(MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
_names.UnionDiscriminatorEnum.IdentifierName,
_names.GetCodeIdentifier(unionField).IdentifierName)));
switch (unionField.Type.Tag)
{
case TypeTag.Void:
section = section.AddStatements(
ExpressionStatement(AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
_names.UnionDiscriminatorProp.IdentifierName,
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
_names.ReaderParameter.IdentifierName,
_names.UnionDiscriminatorProp.IdentifierName))));
break;
default:
section = section.AddStatements(
ExpressionStatement(AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
_names.GetCodeIdentifier(unionField).IdentifierName,
MakeDeserializeMethodRightHandSide(unionField))));
break;
}
section = section.AddStatements(BreakStatement());
yield return section;
}
}
IEnumerable<StatementSyntax> MakeSerializeStatements(TypeDefinition def)
{
if (def.UnionInfo != null)
{
yield return ExpressionStatement(AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
_names.WriterParameter.IdentifierName,
_names.UnionDiscriminatorProp.IdentifierName),
_names.UnionDiscriminatorProp.IdentifierName));
yield return SwitchStatement(_names.UnionDiscriminatorProp.IdentifierName)
.WithOpenParenToken(Token(SyntaxKind.OpenParenToken))
.WithCloseParenToken(Token(SyntaxKind.CloseParenToken))
.AddSections(MakeSerializeMethodSwitchSections(def).ToArray());
}
var nondiscFields = def.Fields.Where(f => !f.DiscValue.HasValue && f.Type.Tag != TypeTag.Void);
foreach (var field in nondiscFields)
{
var asmt = MakeSerializeMethodFieldAssignment(field);
if (asmt != null)
{
yield return asmt;
}
}
}
IEnumerable<StatementSyntax> MakeApplyDefaultsStatements(TypeDefinition def)
{
var relevantFields = def.Fields.Where(
f => !f.DiscValue.HasValue &&
f.Type.Tag != TypeTag.Void &&
f.DefaultValueIsExplicit &&
!f.Type.IsValueType);
foreach (var field in relevantFields)
{
var asmt = MakeApplyDefaultsMethodFieldAssignment(field);
if (asmt != null)
{
yield return asmt;
}
}
}
MemberDeclarationSyntax MakeSerializeMethod(TypeDefinition def)
{
return MethodDeclaration(PredefinedType(
Token(SyntaxKind.VoidKeyword)),
_names.SerializeMethod.Identifier)
.AddModifiers(Public)
.AddParameterListParameters(
Parameter(_names.WriterParameter.Identifier)
.WithType(_names.WriterStruct.IdentifierName))
.AddBodyStatements(MakeSerializeStatements(def).ToArray());
}
MemberDeclarationSyntax MakeSerializeInterfaceMethod()
{
return MethodDeclaration(PredefinedType(Token(SyntaxKind.VoidKeyword)),
Identifier(nameof(Capnp.ICapnpSerializable.Serialize)))
.WithExplicitInterfaceSpecifier(
ExplicitInterfaceSpecifier(IdentifierName(nameof(Capnp.ICapnpSerializable))))
.AddParameterListParameters(
Parameter(_names.AnonymousParameter.Identifier)
.WithType(Type<Capnp.SerializerState>()))
.AddBodyStatements(
ExpressionStatement(
InvocationExpression(_names.SerializeMethod.IdentifierName)
.AddArgumentListArguments(
Argument(
InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
_names.AnonymousParameter.IdentifierName,
GenericName(Identifier(nameof(Capnp.SerializerState.Rewrap)))
.AddTypeArgumentListArguments(_names.WriterStruct.IdentifierName)))))));
}
MemberDeclarationSyntax MakeApplyDefaultsMethod(TypeDefinition def)
{
return MethodDeclaration(PredefinedType(
Token(SyntaxKind.VoidKeyword)),
_names.ApplyDefaultsMethod.Identifier)
.AddModifiers(Public)
.AddBodyStatements(MakeApplyDefaultsStatements(def).ToArray());
}
IEnumerable<StatementSyntax> MakeDeserializeStatements(TypeDefinition def)
{
var relevantFields = def.Fields.Where(
f => !f.DiscValue.HasValue &&
f.Type.Tag != TypeTag.Void);
foreach (var field in relevantFields)
{
var rhs = MakeDeserializeMethodRightHandSide(field);
if (rhs != null)
{
yield return ExpressionStatement(AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
_names.GetCodeIdentifier(field).IdentifierName,
rhs));
}
}
}
MemberDeclarationSyntax MakeDeserializeMethod(TypeDefinition def)
{
var stmts = new List<StatementSyntax>();
stmts.Add(LocalDeclarationStatement(
VariableDeclaration(IdentifierName("var"))
.AddVariables(
VariableDeclarator(_names.ReaderParameter.Identifier)
.WithInitializer(
EqualsValueClause(
InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
_names.ReaderStruct.IdentifierName,
_names.ReaderCreateMethod.IdentifierName))
.AddArgumentListArguments(
Argument(_names.AnonymousParameter.IdentifierName)))))));
if (def.UnionInfo != null)
{
stmts.Add(SwitchStatement(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
_names.ReaderParameter.IdentifierName,
_names.UnionDiscriminatorProp.IdentifierName))
.WithOpenParenToken(Token(SyntaxKind.OpenParenToken))
.WithCloseParenToken(Token(SyntaxKind.CloseParenToken))
.AddSections(MakeDeserializeMethodSwitchSections(def).ToArray()));
}
stmts.AddRange(MakeDeserializeStatements(def));
stmts.Add(ExpressionStatement(InvocationExpression(
_names.ApplyDefaultsMethod.IdentifierName)));
return MethodDeclaration(PredefinedType(Token(SyntaxKind.VoidKeyword)),
Identifier(nameof(Capnp.ICapnpSerializable.Deserialize)))
.WithExplicitInterfaceSpecifier(
ExplicitInterfaceSpecifier(IdentifierName(nameof(Capnp.ICapnpSerializable))))
.AddParameterListParameters(
Parameter(_names.AnonymousParameter.Identifier)
.WithType(Type<Capnp.DeserializerState>()))
.AddBodyStatements(stmts.ToArray());
}
IEnumerable<MemberDeclarationSyntax> EnumerateDomainClassMembers(TypeDefinition def)
{
yield return MakeDeserializeMethod(def);
if (def.UnionInfo != null)
{
yield return MakeUnionDiscriminatorField();
yield return MakeUnionContentField();
yield return MakeUnionDiscriminatorProperty(def);
}
yield return MakeSerializeMethod(def);
yield return MakeSerializeInterfaceMethod();
yield return MakeApplyDefaultsMethod(def);
foreach (var field in def.Fields)
{
var decl = MakeField(field);
if (decl != null)
yield return decl;
}
}
public MemberDeclarationSyntax[] MakeDomainClassMembers(TypeDefinition def)
{
return EnumerateDomainClassMembers(def).ToArray();
}
}
}

View File

@ -1,601 +0,0 @@
using CapnpC.Model;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
namespace CapnpC.Generator
{
enum NameUsage
{
Default,
Interface,
Proxy,
Skeleton,
Namespace
}
enum TypeUsage
{
NotRelevant,
DomainClass,
DomainClassNullable,
Reader,
Writer
}
class GenNames
{
readonly Dictionary<Field, Name> _fieldNameMap = new Dictionary<Field, Name>();
public NameSyntax TopNamespace { get; set; }
public Name ReaderStruct { get; }
public Name ReaderParameter { get; }
public Name WriterParameter { get; }
public Name WriterStruct { get; }
public Name ReaderCreateMethod { get; }
public Name ReaderContextField { get; }
public Name ContextParameter { get; }
public Name GroupReaderContextArg { get; }
public Name GroupWriterContextArg { get; }
public Name UnionDiscriminatorEnum { get; }
public Name UnionDiscriminatorProp { get; }
public Name UnionDiscriminatorUndefined { get; }
public Name UnionDiscriminatorField { get; }
public Name UnionContentField { get; }
public Name AnonymousParameter { get; }
public Name CancellationTokenParameter { get; }
public Name ParamsLocal { get; }
public Name DeserializerLocal { get; }
public Name SerializerLocal { get; }
public Name ResultLocal { get; }
public Name SerializeMethod { get; }
public Name ApplyDefaultsMethod { get; }
public Name InstLocalName { get; }
public string ParamsStructFormat { get; }
public string ResultStructFormat { get; }
public string PropertyNamedLikeTypeRenameFormat { get; }
public string GenericTypeParameterFormat { get; }
public Name PipeliningExtensionsClassName { get; }
public string MemberAccessPathNameFormat { get; }
public Name TaskParameter { get; }
public Name EagerMethod { get; }
public GenNames(GeneratorOptions options)
{
TopNamespace = new Name(options.TopNamespaceName).IdentifierName;
ReaderStruct = new Name(options.ReaderStructName);
WriterStruct = new Name(options.WriterStructName);
ReaderParameter = new Name(options.ReaderParameterName);
WriterParameter = new Name(options.WriterParameterName);
ReaderCreateMethod = new Name(options.ReaderCreateMethodName);
ReaderContextField = new Name(options.ReaderContextFieldName);
ContextParameter = new Name(options.ContextParameterName);
GroupReaderContextArg = new Name(options.GroupReaderContextArgName);
GroupWriterContextArg = new Name(options.GroupWriterContextArgName);
UnionDiscriminatorEnum = new Name(options.UnionDisciminatorEnumName);
UnionDiscriminatorProp = new Name(options.UnionDiscriminatorPropName);
UnionDiscriminatorUndefined = new Name(options.UnionDisciminatorUndefinedName);
UnionDiscriminatorField = new Name(options.UnionDiscriminatorFieldName);
UnionContentField = new Name(options.UnionContentFieldName);
SerializeMethod = new Name(options.SerializeMethodName);
ApplyDefaultsMethod = new Name(options.ApplyDefaultsMethodName);
AnonymousParameter = new Name(options.AnonymousParameterName);
CancellationTokenParameter = new Name(options.CancellationTokenParameterName);
ParamsLocal = new Name(options.ParamsLocalName);
DeserializerLocal = new Name(options.DeserializerLocalName);
SerializerLocal = new Name(options.SerializerLocalName);
ResultLocal = new Name(options.ResultLocalName);
InstLocalName = new Name(options.InstLocalName);
ParamsStructFormat = options.ParamsStructFormat;
ResultStructFormat = options.ResultStructFormat;
PropertyNamedLikeTypeRenameFormat = options.PropertyNamedLikeTypeRenameFormat;
GenericTypeParameterFormat = options.GenericTypeParameterFormat;
PipeliningExtensionsClassName = new Name(options.PipeliningExtensionsClassName);
MemberAccessPathNameFormat = options.MemberAccessPathNameFormat;
TaskParameter = new Name(options.TaskParameterName);
EagerMethod = new Name(options.EagerMethodName);
}
public Name MakeTypeName(TypeDefinition def, NameUsage usage = NameUsage.Default)
{
if (def.Tag == TypeTag.Group)
{
return new Name(SyntaxHelpers.MakeAllLower(def.Name));
}
else
{
string name;
switch (usage)
{
case NameUsage.Default:
if (def.Tag == TypeTag.Interface)
goto case NameUsage.Interface;
switch (def.SpecialName)
{
case SpecialName.NothingSpecial:
name = def.Name;
break;
case SpecialName.MethodParamsStruct:
name = MakeParamsStructName(def.UsingMethod);
break;
case SpecialName.MethodResultStruct:
name = MakeResultStructName(def.UsingMethod);
break;
default:
throw new NotImplementedException();
}
break;
case NameUsage.Namespace:
name = def.Name;
break;
case NameUsage.Interface:
name = "I" + def.Name;
break;
case NameUsage.Proxy:
name = def.Name + "Proxy";
break;
case NameUsage.Skeleton:
name = def.Name + "Skeleton";
break;
default:
throw new NotImplementedException();
}
return new Name(name);
}
}
public SimpleNameSyntax MakeGenericTypeName(TypeDefinition def, NameUsage usage = NameUsage.Default)
{
var name = MakeTypeName(def, usage);
if (def.GenericParameters.Count > 0)
{
return GenericName(name.Identifier)
.AddTypeArgumentListArguments(def
.GenericParameters
.Select(p => GetGenericTypeParameter(p).IdentifierName).ToArray());
}
else
{
return name.IdentifierName;
}
}
TypeSyntax ResolveGenericParameter(GenericParameter p, Model.Type boundType, TypeDefinition def)
{
var type = boundType.ResolveGenericParameter(p);
return MakeTypeSyntax(type, def, TypeUsage.DomainClass);
}
public SimpleNameSyntax MakeGenericTypeName(TypeDefinition def, Model.Type boundType, NameUsage usage = NameUsage.Default)
{
var name = MakeTypeName(def, usage);
if (def.GenericParameters.Count > 0)
{
return GenericName(name.Identifier)
.AddTypeArgumentListArguments(def
.GetLocalTypeParameters()
.Select(p => ResolveGenericParameter(p, boundType, def)).ToArray());
}
else
{
return name.IdentifierName;
}
}
public SimpleNameSyntax MakeGenericTypeNameForAttribute(TypeDefinition def, NameUsage usage)
{
var name = MakeTypeName(def, usage);
if (def.GenericParameters.Count > 0)
{
return GenericName(name.Identifier).AddTypeArgumentListArguments();
}
else
{
return name.IdentifierName;
}
}
public static NameSyntax NamespaceName(string[] @namespace)
{
NameSyntax ident = null;
if (@namespace != null)
{
ident = IdentifierName(SyntaxHelpers.MakeCamel(@namespace[0]));
foreach (string name in @namespace.Skip(1))
{
var temp = IdentifierName(SyntaxHelpers.MakeCamel(name));
ident = QualifiedName(ident, temp);
}
}
return ident;
}
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
// there are multiple objects with identical name up the hierarchy. We will need a more
// sophisticated algorithm.
var scopeSet = new HashSet<TypeDefinition>();
//while (scope != null)
//{
// scopeSet.Add(scope);
// scope = scope.DeclaringElement as TypeDefinition;
//}
if (type.Definition != null)
{
var stack = new Stack<SimpleNameSyntax>();
var def = type.Definition;
stack.Push(MakeGenericTypeName(def, type, NameUsage.Default));
while (def.DeclaringElement is TypeDefinition pdef && !scopeSet.Contains(pdef))
{
stack.Push(MakeGenericTypeName(pdef, type, NameUsage.Namespace));
def = pdef;
}
var qtype =
GetNamespaceFor(type.Definition)
?? GetNamespaceFor(scope)
?? TopNamespace;
foreach (var name in stack)
{
qtype = QualifiedName(qtype, name);
}
return qtype;
}
else
{
return GetGenericTypeParameter(type.Parameter.Name).IdentifierName;
}
}
public TypeSyntax MakeListSerializerSyntax(Model.Type elementType, TypeDefinition scope)
{
switch (elementType.Tag)
{
case TypeTag.AnyPointer:
case TypeTag.StructPointer:
case TypeTag.ListPointer:
return SyntaxHelpers.Type<Capnp.ListOfPointersSerializer<Capnp.DynamicSerializerState>>();
case TypeTag.CapabilityPointer:
return SyntaxHelpers.Type<Capnp.ListOfCapsSerializer<Capnp.Rpc.BareProxy>>();
case TypeTag.Data:
return SyntaxHelpers.Type<Capnp.ListOfPointersSerializer<
Capnp.ListOfPrimitivesSerializer<byte>>>();
case TypeTag.Enum:
return GenericName("ListOfPrimitivesSerializer")
.AddTypeArgumentListArguments(MakeTypeSyntax(elementType, scope, TypeUsage.Writer));
case TypeTag.Group:
case TypeTag.Struct:
return GenericName("ListOfStructsSerializer")
.AddTypeArgumentListArguments(MakeTypeSyntax(elementType, scope, TypeUsage.Writer));
case TypeTag.Interface:
return GenericName("ListOfCapsSerializer")
.AddTypeArgumentListArguments(MakeTypeSyntax(elementType, scope, TypeUsage.Writer));
case TypeTag.List:
return GenericName("ListOfPointersSerializer")
.AddTypeArgumentListArguments(MakeTypeSyntax(elementType, scope, TypeUsage.Writer));
case TypeTag.Text:
return SyntaxHelpers.Type<Capnp.ListOfTextSerializer>();
case TypeTag.Void:
return SyntaxHelpers.Type<Capnp.ListOfEmptySerializer>();
case TypeTag.Bool:
return SyntaxHelpers.Type<Capnp.ListOfBitsSerializer>();
case TypeTag.F32:
return SyntaxHelpers.Type<Capnp.ListOfPrimitivesSerializer<float>>();
case TypeTag.F64:
return SyntaxHelpers.Type<Capnp.ListOfPrimitivesSerializer<double>>();
case TypeTag.S8:
return SyntaxHelpers.Type<Capnp.ListOfPrimitivesSerializer<sbyte>>();
case TypeTag.U8:
return SyntaxHelpers.Type<Capnp.ListOfPrimitivesSerializer<byte>>();
case TypeTag.S16:
return SyntaxHelpers.Type<Capnp.ListOfPrimitivesSerializer<short>>();
case TypeTag.U16:
case TypeTag.AnyEnum:
return SyntaxHelpers.Type<Capnp.ListOfPrimitivesSerializer<ushort>>();
case TypeTag.S32:
return SyntaxHelpers.Type<Capnp.ListOfPrimitivesSerializer<int>>();
case TypeTag.U32:
return SyntaxHelpers.Type<Capnp.ListOfPrimitivesSerializer<uint>>();
case TypeTag.S64:
return SyntaxHelpers.Type<Capnp.ListOfPrimitivesSerializer<long>>();
case TypeTag.U64:
return SyntaxHelpers.Type<Capnp.ListOfPrimitivesSerializer<ulong>>();
default:
throw new NotImplementedException("Unexpected type tag, don't know how to deal with this");
}
}
TypeSyntax MaybeNullableValueType(TypeSyntax typeSyntax, TypeUsage usage)
{
switch (usage)
{
case TypeUsage.DomainClassNullable:
return NullableType(typeSyntax);
default:
return typeSyntax;
}
}
public TypeSyntax MakeTypeSyntax(Model.Type type, TypeDefinition scope, TypeUsage usage)
{
switch (type.Tag)
{
case TypeTag.AnyEnum:
return MaybeNullableValueType(SyntaxHelpers.Type<ushort>(), usage);
case TypeTag.CapabilityPointer:
if (type.Parameter != null)
{
return GetQName(type, scope);
}
else
{
return SyntaxHelpers.Type<Capnp.Rpc.BareProxy>();
}
case TypeTag.AnyPointer:
case TypeTag.StructPointer:
switch (usage)
{
case TypeUsage.Reader:
return SyntaxHelpers.Type<Capnp.DeserializerState>();
case TypeUsage.Writer:
return SyntaxHelpers.Type<Capnp.DynamicSerializerState>();
case TypeUsage.DomainClass:
case TypeUsage.DomainClassNullable:
if (type.Parameter != null)
{
return GetQName(type, scope);
}
else
{
return SyntaxHelpers.Type<Capnp.AnyPointer>();
}
default:
throw new NotImplementedException();
}
case TypeTag.Bool:
return MaybeNullableValueType(SyntaxHelpers.Type<bool>(), usage);
case TypeTag.Data:
switch (usage)
{
case TypeUsage.Reader:
case TypeUsage.DomainClass:
case TypeUsage.DomainClassNullable:
return SyntaxHelpers.Type<IReadOnlyList<byte>>();
case TypeUsage.Writer:
return SyntaxHelpers.Type<Capnp.ListOfPrimitivesSerializer<byte>>();
default:
throw new NotImplementedException();
}
case TypeTag.Enum:
return MaybeNullableValueType(GetQName(type, scope), usage);
case TypeTag.Interface:
return GetQName(type, scope);
case TypeTag.Struct:
case TypeTag.Group:
switch (usage)
{
case TypeUsage.Writer:
return QualifiedName(GetQName(type, scope), WriterStruct.IdentifierName);
case TypeUsage.Reader:
return QualifiedName(GetQName(type, scope), ReaderStruct.IdentifierName);
case TypeUsage.DomainClass:
case TypeUsage.DomainClassNullable:
return GetQName(type, scope);
default:
throw new NotImplementedException();
}
case TypeTag.F32:
return MaybeNullableValueType(SyntaxHelpers.Type<float>(), usage);
case TypeTag.F64:
return MaybeNullableValueType(SyntaxHelpers.Type<double>(), usage);
case TypeTag.List when type.ElementType.Tag == TypeTag.Void && usage != TypeUsage.Writer:
return MaybeNullableValueType(SyntaxHelpers.Type<int>(), usage);
case TypeTag.List:
switch (usage)
{
case TypeUsage.Writer:
return MakeListSerializerSyntax(type.ElementType, scope);
case TypeUsage.Reader:
return GenericName(Identifier("IReadOnlyList"))
.AddTypeArgumentListArguments(MakeTypeSyntax(type.ElementType, scope, TypeUsage.Reader));
case TypeUsage.DomainClass:
case TypeUsage.DomainClassNullable:
return GenericName(Identifier("IReadOnlyList"))
.AddTypeArgumentListArguments(MakeTypeSyntax(type.ElementType, scope, TypeUsage.DomainClass));
default:
throw new NotImplementedException();
}
case TypeTag.ListPointer:
switch (usage)
{
case TypeUsage.Writer:
return SyntaxHelpers.Type<Capnp.SerializerState>();
case TypeUsage.Reader:
return SyntaxHelpers.Type<IReadOnlyList<Capnp.DeserializerState>>();
case TypeUsage.DomainClass:
case TypeUsage.DomainClassNullable:
return SyntaxHelpers.Type<IReadOnlyList<object>>();
default:
throw new NotImplementedException();
}
case TypeTag.S16:
return MaybeNullableValueType(SyntaxHelpers.Type<short>(), usage);
case TypeTag.S32:
return MaybeNullableValueType(SyntaxHelpers.Type<int>(), usage);
case TypeTag.S64:
return MaybeNullableValueType(SyntaxHelpers.Type<long>(), usage);
case TypeTag.S8:
return MaybeNullableValueType(SyntaxHelpers.Type<sbyte>(), usage);
case TypeTag.Text:
return SyntaxHelpers.Type<string>();
case TypeTag.U16:
return MaybeNullableValueType(SyntaxHelpers.Type<ushort>(), usage);
case TypeTag.U32:
return MaybeNullableValueType(SyntaxHelpers.Type<uint>(), usage);
case TypeTag.U64:
return MaybeNullableValueType(SyntaxHelpers.Type<ulong>(), usage);
case TypeTag.U8:
return MaybeNullableValueType(SyntaxHelpers.Type<byte>(), usage);
case TypeTag.Void:
return PredefinedType(Token(SyntaxKind.VoidKeyword));
default:
throw new NotImplementedException("Unexpected type tag, don't know how to deal with this");
}
}
public string MakeParamsStructName(Method method)
{
return string.Format(ParamsStructFormat, method.Name);
}
public string MakeResultStructName(Method method)
{
return string.Format(ResultStructFormat, method.Name);
}
public Name GetCodeIdentifier(Method method)
{
return new Name(SyntaxHelpers.MakeCamel(method.Name));
}
public Name GetCodeIdentifier(Field field)
{
if (_fieldNameMap.TryGetValue(field, out var name))
{
return name;
}
var def = field.DeclaringType;
if (def == null)
{
// Method parameters are internally represented with the same class "Field".
// They do not have a declaring type. Anyway, they don't suffer from the field-name-equals-nested-type-name problem.
return new Name(SyntaxHelpers.MakeCamel(field.Name));
}
var typeNames = new HashSet<Name>(def.NestedTypes.Select(t => MakeTypeName(t)));
typeNames.Add(MakeTypeName(def));
foreach (var member in def.Fields)
{
var memberName = new Name(SyntaxHelpers.MakeCamel(member.Name));
while (typeNames.Contains(memberName))
{
memberName = new Name(string.Format(PropertyNamedLikeTypeRenameFormat, memberName.ToString()));
}
_fieldNameMap.Add(member, memberName);
}
return _fieldNameMap[field];
}
public Name GetGenericTypeParameter(string name)
{
return new Name(string.Format(GenericTypeParameterFormat, name));
}
public Name MakePipeliningSupportExtensionMethodName(IReadOnlyList<Field> path)
{
if (path.Count == 1 && path[0].Offset == 0)
return EagerMethod;
else
return new Name(string.Join("_", path.Select(f => GetCodeIdentifier(f).ToString())));
}
public Name MakeMemberAccessPathFieldName(Method method, IReadOnlyList<Field> path)
{
return new Name(string.Format(MemberAccessPathNameFormat,
method.Name,
MakePipeliningSupportExtensionMethodName(path)));
}
}
}

View File

@ -1,42 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CapnpC.Generator
{
class GeneratorOptions
{
public string TopNamespaceName { get; set; } = "CapnpGen";
public string ReaderStructName { get; set; } = "READER";
public string WriterStructName { get; set; } = "WRITER";
public string ReaderParameterName { get; set; } = "reader";
public string WriterParameterName { get; set; } = "writer";
public string ReaderCreateMethodName { get; set; } = "create";
public string ReaderContextFieldName { get; set; } = "ctx";
public string ContextParameterName { get; set; } = "ctx";
public string GroupReaderContextArgName { get; set; } = "ctx";
public string GroupWriterContextArgName { get; set; } = "ctx";
public string UnionDisciminatorEnumName { get; set; } = "WHICH";
public string UnionDiscriminatorPropName { get; set; } = "which";
public string UnionDiscriminatorFieldName { get; set; } = "_which";
public string UnionDisciminatorUndefinedName { get; set; } = "undefined";
public string UnionContentFieldName { get; set; } = "_content";
public string SerializeMethodName { get; set; } = "serialize";
public string ApplyDefaultsMethodName { get; set; } = "applyDefaults";
public string AnonymousParameterName { get; set; } = "arg_";
public string CancellationTokenParameterName { get; set; } = "cancellationToken_";
public string ParamsLocalName { get; set; } = "in_";
public string DeserializerLocalName { get; set; } = "d_";
public string SerializerLocalName { get; set; } = "s_";
public string ResultLocalName { get; set; } = "r_";
public string ParamsStructFormat { get; set; } = "Params_{0}";
public string ResultStructFormat { get; set; } = "Result_{0}";
public string PropertyNamedLikeTypeRenameFormat { get; set; } = "The{0}";
public string InstLocalName { get; set; } = "inst";
public string GenericTypeParameterFormat { get; set; } = "T{0}";
public string PipeliningExtensionsClassName { get; set; } = "PipeliningSupportExtensions";
public string MemberAccessPathNameFormat { get; set; } = "Path_{0}_{1}";
public string TaskParameterName { get; set; } = "task";
public string EagerMethodName { get; set; } = "Eager";
}
}

View File

@ -1,844 +0,0 @@
using CapnpC.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static CapnpC.Generator.SyntaxHelpers;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Threading.Tasks;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp;
namespace CapnpC.Generator
{
class InterfaceSnippetGen
{
readonly GenNames _names;
public InterfaceSnippetGen(GenNames names)
{
_names = names;
}
TypeSyntax TransformReturnType(Method method)
{
switch (method.Results.Count)
{
case 0:
return IdentifierName(nameof(Task));
case 1:
return GenericName(nameof(Task)).AddTypeArgumentListArguments(
_names.MakeTypeSyntax(method.Results[0].Type, method.DeclaringInterface, TypeUsage.DomainClass));
default:
return GenericName(nameof(Task)).AddTypeArgumentListArguments(
TupleType(SeparatedList(
method.Results.Select(
f => TupleElement(_names.MakeTypeSyntax(f.Type, method.DeclaringInterface, TypeUsage.DomainClass))))));
}
}
ParameterSyntax[] TransformParameters(Method method)
{
var list = new List<ParameterSyntax>();
if (method.Params.Count > 0)
{
var arg0 = method.Params[0];
if (arg0.Name == null)
{
list.Add(Parameter(_names.AnonymousParameter.Identifier)
.WithType(_names.MakeTypeSyntax(arg0.Type, method.DeclaringInterface, TypeUsage.DomainClass)));
}
else
{
foreach (var arg in method.Params)
{
list.Add(Parameter(Identifier(arg.Name))
.WithType(_names.MakeTypeSyntax(arg.Type, method.DeclaringInterface, TypeUsage.DomainClass)));
}
}
}
list.Add(Parameter(_names.CancellationTokenParameter.Identifier)
.WithType(IdentifierName(nameof(CancellationToken)))
.WithDefault(EqualsValueClause(LiteralExpression(
SyntaxKind.DefaultLiteralExpression,
Token(SyntaxKind.DefaultKeyword)))));
return list.ToArray();
}
IEnumerable<TypeParameterSyntax> MakeTypeParameters(TypeDefinition def)
{
foreach (string name in def.GenericParameters)
{
yield return TypeParameter(_names.GetGenericTypeParameter(name).Identifier);
}
}
IEnumerable<TypeParameterConstraintClauseSyntax> MakeTypeParameterConstraints(TypeDefinition def)
{
foreach (string name in def.GenericParameters)
{
yield return TypeParameterConstraintClause(
_names.GetGenericTypeParameter(name).IdentifierName)
.AddConstraints(ClassOrStructConstraint(SyntaxKind.ClassConstraint));
}
}
public MemberDeclarationSyntax MakeInterface(TypeDefinition type)
{
var ifaceDecl = InterfaceDeclaration(_names.MakeTypeName(type, NameUsage.Interface).Identifier)
.AddModifiers(Public)
.AddAttributeLists(
AttributeList()
.AddAttributes(
Attribute(IdentifierName("Proxy"))
.AddArgumentListArguments(
AttributeArgument(
TypeOfExpression(_names.MakeGenericTypeNameForAttribute(type, NameUsage.Proxy)))),
Attribute(IdentifierName("Skeleton"))
.AddArgumentListArguments(
AttributeArgument(
TypeOfExpression(_names.MakeGenericTypeNameForAttribute(type, NameUsage.Skeleton))))));
if (type.GenericParameters.Count > 0)
{
ifaceDecl = ifaceDecl.AddTypeParameterListParameters(MakeTypeParameters(type).ToArray());
}
if (type.Superclasses.Count == 0)
{
ifaceDecl = ifaceDecl.AddBaseListTypes(SimpleBaseType(IdentifierName(nameof(IDisposable))));
}
else
{
foreach (var superClass in type.Superclasses)
{
ifaceDecl = ifaceDecl.AddBaseListTypes(
SimpleBaseType(_names.MakeTypeSyntax(
superClass, type,
TypeUsage.NotRelevant)));
}
}
foreach (var method in type.Methods)
{
var methodDecl = MethodDeclaration(
TransformReturnType(method),
_names.GetCodeIdentifier(method).Identifier)
.AddParameterListParameters(TransformParameters(method))
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken));
if (method.GenericParameters.Count > 0)
{
methodDecl = methodDecl
.AddTypeParameterListParameters(MakeTypeParameters(method).ToArray())
.AddConstraintClauses(MakeTypeParameterConstraints(method).ToArray());
}
ifaceDecl = ifaceDecl.AddMembers(methodDecl);
}
return ifaceDecl;
}
bool IsSubjectToPipelining(Model.Type type, HashSet<Model.Type> visited)
{
if (!visited.Add(type))
return false;
switch (type.Tag)
{
case TypeTag.AnyPointer:
case TypeTag.CapabilityPointer:
case TypeTag.Interface:
case TypeTag.ListPointer:
case TypeTag.StructPointer:
return true;
case TypeTag.List:
return IsSubjectToPipelining(type.ElementType, visited);
case TypeTag.Struct:
return type.Fields.Any(f => IsSubjectToPipelining(f.Type, visited));
default:
return false;
}
}
bool IsSubjectToPipelining(Method method)
{
return method.Results.Any(r => IsSubjectToPipelining(r.Type, new HashSet<Model.Type>()));
}
IEnumerable<ExpressionSyntax> MakeProxyCallInitializerAssignments(Method method)
{
foreach (var methodParam in method.Params)
{
yield return AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
_names.GetCodeIdentifier(methodParam).IdentifierName,
IdentifierName(methodParam.Name));
}
}
IEnumerable<ArgumentSyntax> MakeProxyReturnResultTupleElements(Method method)
{
foreach (var item in method.ResultStruct.Fields)
{
yield return Argument(MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
_names.ResultLocal.IdentifierName,
_names.GetCodeIdentifier(item).IdentifierName));
}
}
StatementSyntax MakeProxyReturnResult(Method method)
{
if (method.ResultStruct.Definition.SpecialName == SpecialName.MethodResultStruct)
{
if (method.ResultStruct.Fields.Count == 0)
{
return ReturnStatement();
}
else
{
return ReturnStatement(TupleExpression()
.AddArguments(MakeProxyReturnResultTupleElements(method).ToArray()));
}
}
else
{
return ReturnStatement(_names.ResultLocal.IdentifierName);
}
}
StatementSyntax MakeProxyCreateResult(Method method)
{
var resultType = method.ResultStruct;
var domainType = _names.MakeTypeSyntax(resultType, method.DeclaringInterface, TypeUsage.DomainClass);
var createDomain = InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
IdentifierName(nameof(Capnp.CapnpSerializable)),
GenericName(nameof(Capnp.CapnpSerializable.Create))
.AddTypeArgumentListArguments(domainType)))
.AddArgumentListArguments(
Argument(_names.DeserializerLocal.IdentifierName));
return LocalDeclarationStatement(
VariableDeclaration(
IdentifierName("var"))
.WithVariables(
SingletonSeparatedList(
VariableDeclarator(
_names.ResultLocal.Identifier)
.WithInitializer(
EqualsValueClause(createDomain)))));
}
IEnumerable<TypeParameterSyntax> MakeTypeParameters(Method method)
{
foreach (string name in method.GenericParameters)
{
yield return TypeParameter(_names.GetGenericTypeParameter(name).Identifier);
}
}
IEnumerable<TypeParameterConstraintClauseSyntax> MakeTypeParameterConstraints(Method method)
{
foreach (string name in method.GenericParameters)
{
yield return TypeParameterConstraintClause(
_names.GetGenericTypeParameter(name).IdentifierName)
.AddConstraints(ClassOrStructConstraint(SyntaxKind.ClassConstraint));
}
}
public MemberDeclarationSyntax MakeProxy(TypeDefinition type)
{
var classDecl = ClassDeclaration(_names.MakeTypeName(type, NameUsage.Proxy).Identifier)
.AddModifiers(Public)
.AddBaseListTypes(
SimpleBaseType(Type<Capnp.Rpc.Proxy>()),
SimpleBaseType(_names.MakeGenericTypeName(type, NameUsage.Interface)));
if (type.GenericParameters.Count > 0)
{
classDecl = classDecl
.AddTypeParameterListParameters(MakeTypeParameters(type).ToArray())
.AddConstraintClauses(MakeTypeParameterConstraints(type).ToArray());
}
var allMethods =
from c in Types.FromDefinition(type).AllImplementedClasses
from m in c.Definition.Methods
select m;
foreach (var method in allMethods)
{
var bodyStmts = new List<StatementSyntax>();
bodyStmts.Add(LocalDeclarationStatement(
VariableDeclaration(
IdentifierName("var"))
.WithVariables(
SingletonSeparatedList(
VariableDeclarator(
_names.ParamsLocal.Identifier)
.WithInitializer(
EqualsValueClause(
InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
IdentifierName(nameof(Capnp.SerializerState)),
GenericName(
Identifier(nameof(Capnp.SerializerState.CreateForRpc)))
.WithTypeArgumentList(
TypeArgumentList(
SingletonSeparatedList(
_names.MakeTypeSyntax(
method.ParamsStruct,
method.ParamsStruct.Definition,
TypeUsage.Writer))))))))))));
if (method.ParamsStruct.Definition.SpecialName == SpecialName.MethodParamsStruct)
{
bodyStmts.Add(LocalDeclarationStatement(
VariableDeclaration(
IdentifierName("var"))
.WithVariables(
SingletonSeparatedList<VariableDeclaratorSyntax>(
VariableDeclarator(
_names.AnonymousParameter.Identifier)
.WithInitializer(
EqualsValueClause(
ObjectCreationExpression(
_names.MakeTypeSyntax(
method.ParamsStruct,
method.ParamsStruct.Definition,
TypeUsage.DomainClass))
.WithArgumentList(
ArgumentList())
.WithInitializer(
InitializerExpression(
SyntaxKind.ObjectInitializerExpression,
SeparatedList<ExpressionSyntax>(
CommonSnippetGen.MakeCommaSeparatedList(
MakeProxyCallInitializerAssignments(method)).ToArray())))))))));
}
bodyStmts.Add(ExpressionStatement(
InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
_names.AnonymousParameter.IdentifierName,
_names.SerializeMethod.IdentifierName))
.AddArgumentListArguments(
Argument(_names.ParamsLocal.IdentifierName))));
var call = InvocationExpression(IdentifierName(nameof(Capnp.Rpc.BareProxy.Call)))
.AddArgumentListArguments(
Argument(
LiteralExpression(SyntaxKind.NumericLiteralExpression,
Literal(method.DeclaringInterface.Id))),
Argument(
LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(method.Id))),
Argument(
InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
_names.ParamsLocal.IdentifierName,
GenericName(nameof(Capnp.SerializerState.Rewrap))
.AddTypeArgumentListArguments(Type<Capnp.DynamicSerializerState>())))
.AddArgumentListArguments()),
Argument(
LiteralExpression(SyntaxKind.FalseLiteralExpression)),
Argument(
_names.CancellationTokenParameter.IdentifierName));
MethodDeclarationSyntax methodDecl;
if (IsSubjectToPipelining(method))
{
methodDecl = MethodDeclaration(
TransformReturnType(method),
_names.GetCodeIdentifier(method).Identifier)
.AddParameterListParameters(TransformParameters(method))
.AddModifiers(Public);
var pipelineAwareCall = InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
IdentifierName(nameof(Capnp.Rpc.Impatient)),
IdentifierName(nameof(Capnp.Rpc.Impatient.MakePipelineAware))))
.AddArgumentListArguments(
Argument(call),
Argument(SimpleLambdaExpression(
Parameter(_names.DeserializerLocal.Identifier),
Block(
MakeProxyCreateResult(method),
MakeProxyReturnResult(method)))));
bodyStmts.Add(ReturnStatement(pipelineAwareCall));
}
else
{
methodDecl = MethodDeclaration(
TransformReturnType(method),
_names.GetCodeIdentifier(method).Identifier)
.AddParameterListParameters(TransformParameters(method))
.AddModifiers(Public, Token(SyntaxKind.AsyncKeyword));
var whenReturned = MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
call,
IdentifierName(nameof(Capnp.Rpc.IPromisedAnswer.WhenReturned)));
var assignAwaited = LocalDeclarationStatement(
VariableDeclaration(
IdentifierName("var"))
.AddVariables(
VariableDeclarator(
_names.DeserializerLocal.Identifier)
.WithInitializer(
EqualsValueClause(
AwaitExpression(whenReturned)))));
bodyStmts.Add(assignAwaited);
bodyStmts.Add(MakeProxyCreateResult(method));
bodyStmts.Add(MakeProxyReturnResult(method));
}
if (method.GenericParameters.Count > 0)
{
methodDecl = methodDecl
.AddTypeParameterListParameters(MakeTypeParameters(method).ToArray())
.AddConstraintClauses(MakeTypeParameterConstraints(method).ToArray());
}
methodDecl = methodDecl.AddBodyStatements(bodyStmts.ToArray());
classDecl = classDecl.AddMembers(methodDecl);
}
return classDecl;
}
IEnumerable<ArgumentSyntax> MakeSkeletonSetMethodTableArguments(TypeDefinition def)
{
foreach (var method in def.Methods)
{
if (method.GenericParameters.Count > 0)
{
yield return Argument(
GenericName(_names.GetCodeIdentifier(method).ToString())
.AddTypeArgumentListArguments(
Enumerable.Repeat(
Type<Capnp.AnyPointer>(),
method.GenericParameters.Count).ToArray()));
}
else
{
yield return Argument(_names.GetCodeIdentifier(method).IdentifierName);
}
}
}
IEnumerable<ExpressionSyntax> MakeSkeletonMethodResultStructInitializer(Method method)
{
foreach (var arg in method.Results)
{
yield return AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
_names.GetCodeIdentifier(arg).IdentifierName,
IdentifierName(arg.Name));
}
}
IEnumerable<ArgumentSyntax> MakeSkeletonMethodCallArgs(Method method)
{
foreach (var arg in method.ParamsStruct.Fields)
{
yield return Argument(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
_names.ParamsLocal.IdentifierName,
_names.GetCodeIdentifier(arg).IdentifierName));
}
}
StatementSyntax MakeSkeletonMethodSerializerLocalDeclaration(Method method)
{
return LocalDeclarationStatement(
VariableDeclaration(
IdentifierName("var"))
.WithVariables(
SingletonSeparatedList(
VariableDeclarator(
_names.SerializerLocal.Identifier)
.WithInitializer(
EqualsValueClause(
InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
IdentifierName(nameof(Capnp.SerializerState)),
GenericName(
Identifier(nameof(Capnp.SerializerState.CreateForRpc)))
.WithTypeArgumentList(
TypeArgumentList(
SingletonSeparatedList(
_names.MakeTypeSyntax(
method.ResultStruct,
method.ResultStruct.Definition,
TypeUsage.Writer)))))))))));
}
CSharpSyntaxNode MakeMaybeTailCallLambdaBody(Method method)
{
var block = Block(
MakeSkeletonMethodSerializerLocalDeclaration(method));
if (method.ResultStruct.Definition.SpecialName == SpecialName.MethodResultStruct)
{
block = block.AddStatements(
LocalDeclarationStatement(
VariableDeclaration(
IdentifierName("var"))
.AddVariables(
VariableDeclarator(_names.ResultLocal.Identifier)
.WithInitializer(EqualsValueClause(ObjectCreationExpression(
_names.MakeTypeSyntax(
method.ResultStruct,
method.ResultStruct.Definition,
TypeUsage.DomainClass))
.WithInitializer(
InitializerExpression(SyntaxKind.ObjectInitializerExpression)
.AddExpressions(
MakeSkeletonMethodResultStructInitializer(method).ToArray())))))));
}
if (method.Results.Count > 0)
{
block = block.AddStatements(
ExpressionStatement(
InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
_names.ResultLocal.IdentifierName,
_names.SerializeMethod.IdentifierName))
.AddArgumentListArguments(
Argument(_names.SerializerLocal.IdentifierName))));
}
block = block.AddStatements(
ReturnStatement(_names.SerializerLocal.IdentifierName));
return block;
}
IEnumerable<StatementSyntax> MakeSkeletonMethodBody(Method method)
{
var call = InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
IdentifierName(SkeletonWorder.ImplName),
_names.GetCodeIdentifier(method).IdentifierName));
if (method.Params.Count > 0)
{
var paramsType = method.ParamsStruct;
var domainType = _names.MakeTypeSyntax(paramsType, method.ParamsStruct.Definition, TypeUsage.DomainClass);
var createDomain = InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
IdentifierName(nameof(Capnp.CapnpSerializable)),
GenericName(nameof(Capnp.CapnpSerializable.Create))
.AddTypeArgumentListArguments(domainType)))
.AddArgumentListArguments(
Argument(_names.DeserializerLocal.IdentifierName));
if (method.ParamsStruct.Definition.SpecialName == SpecialName.MethodParamsStruct)
{
yield return LocalDeclarationStatement(
VariableDeclaration(
IdentifierName("var"))
.AddVariables(
VariableDeclarator(_names.ParamsLocal.Identifier)
.WithInitializer(EqualsValueClause(createDomain))));
call = call.AddArgumentListArguments(
MakeSkeletonMethodCallArgs(method).ToArray());
}
else
{
call = call.AddArgumentListArguments(
Argument(createDomain));
}
}
call = call.AddArgumentListArguments(
Argument(
_names.CancellationTokenParameter.IdentifierName));
if (method.Results.Count == 0)
{
var awaitCall = AwaitExpression(call);
yield return ExpressionStatement(awaitCall);
yield return MakeSkeletonMethodSerializerLocalDeclaration(method);
yield return ReturnStatement(_names.SerializerLocal.IdentifierName);
}
else
{
ExpressionSyntax lambdaArg;
if (method.ResultStruct.Definition.SpecialName == SpecialName.MethodResultStruct)
{
if (method.Results.Count == 1)
{
lambdaArg = SimpleLambdaExpression(
Parameter(Identifier(method.Results.Single().Name)),
MakeMaybeTailCallLambdaBody(method));
}
else
{
lambdaArg = ParenthesizedLambdaExpression(
MakeMaybeTailCallLambdaBody(method))
.AddParameterListParameters(
method.Results.Select(arg => Parameter(Identifier(arg.Name))).ToArray());
}
}
else
{
lambdaArg = SimpleLambdaExpression(
Parameter(_names.ResultLocal.Identifier),
MakeMaybeTailCallLambdaBody(method));
}
var maybeTailCall = InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
IdentifierName(nameof(Capnp.Rpc.Impatient)),
IdentifierName(nameof(Capnp.Rpc.Impatient.MaybeTailCall))))
.AddArgumentListArguments(
Argument(call),
Argument(lambdaArg));
yield return ReturnStatement(maybeTailCall);
}
}
IEnumerable<MemberDeclarationSyntax> MakeSkeletonMethods(TypeDefinition def)
{
foreach (var method in def.Methods)
{
var methodDecl = MethodDeclaration(
Type<Task<Capnp.Rpc.AnswerOrCounterquestion>>(),
_names.GetCodeIdentifier(method).Identifier)
.AddParameterListParameters(
Parameter(_names.DeserializerLocal.Identifier)
.WithType(Type<Capnp.DeserializerState>()),
Parameter(_names.CancellationTokenParameter.Identifier)
.WithType(Type<CancellationToken>()))
.AddBodyStatements(
MakeSkeletonMethodBody(method).ToArray());
if (method.Results.Count == 0)
{
methodDecl = methodDecl.AddModifiers(Async);
}
if (method.GenericParameters.Count > 0)
{
methodDecl = methodDecl
.AddTypeParameterListParameters(MakeTypeParameters(method).ToArray())
.AddConstraintClauses(MakeTypeParameterConstraints(method).ToArray());
}
yield return methodDecl;
}
}
public MemberDeclarationSyntax MakeSkeleton(TypeDefinition type)
{
var name = _names.MakeTypeName(type, NameUsage.Skeleton).Identifier;
var classDecl = ClassDeclaration(name)
.AddModifiers(Public)
.AddBaseListTypes(
SimpleBaseType(
GenericName(nameof(Capnp.Rpc.Skeleton))
.AddTypeArgumentListArguments(
_names.MakeGenericTypeName(type, NameUsage.Interface))))
.AddMembers(
// C'tor
ConstructorDeclaration(name)
.AddModifiers(Public)
.AddBodyStatements(
ExpressionStatement(
InvocationExpression(
IdentifierName(SkeletonWorder.SetMethodTableName))
.AddArgumentListArguments(
MakeSkeletonSetMethodTableArguments(type).ToArray()))),
// InterfaceId
PropertyDeclaration(Type<ulong>(), nameof(Capnp.Rpc.Skeleton<object>.InterfaceId))
.AddModifiers(Public, Override)
.WithExpressionBody(
ArrowExpressionClause(
ValueOf(type.Id)))
.WithSemicolonToken(
Token(SyntaxKind.SemicolonToken)));
if (type.GenericParameters.Count > 0)
{
classDecl = classDecl
.AddTypeParameterListParameters(MakeTypeParameters(type).ToArray())
.AddConstraintClauses(MakeTypeParameterConstraints(type).ToArray());
}
classDecl = classDecl.AddMembers(MakeSkeletonMethods(type).ToArray());
return classDecl;
}
public bool RequiresPipeliningSupport(TypeDefinition type)
{
return type.Methods.Any(m => ExpandPipeliningPaths(m).Any());
}
IEnumerable<IReadOnlyList<Field>> ExpandPipeliningPaths(Method method)
{
var stack = new Stack<List<Field>>();
foreach (var field in method.ResultStruct.Fields)
{
stack.Push(new List<Field>() { field });
}
while (stack.Count > 0)
{
var path = stack.Pop();
var last = path[path.Count - 1];
switch (last.Type.Tag)
{
case TypeTag.Interface:
case TypeTag.CapabilityPointer:
yield return path;
break;
case TypeTag.Struct:
foreach (var field in last.Type.Fields)
{
if (path.Contains(field))
{
// Recursive structs protection
continue;
}
var copy = new List<Field>();
copy.AddRange(path);
copy.Add(field);
stack.Push(copy);
}
break;
}
}
}
readonly HashSet<(string, string)> _existingExtensionMethods = new HashSet<(string, string)>();
public MemberDeclarationSyntax MakePipeliningSupport(TypeDefinition type)
{
var classDecl = ClassDeclaration(_names.PipeliningExtensionsClassName.Identifier)
.AddModifiers(Public, Static, Partial);
foreach (var method in type.Methods)
{
foreach (var path in ExpandPipeliningPaths(method))
{
var accessPath = _names.MakeMemberAccessPathFieldName(method, path);
var methodName = _names.MakePipeliningSupportExtensionMethodName(path);
var capType = path[path.Count - 1].Type;
var capTypeSyntax = _names.MakeTypeSyntax(capType, null, TypeUsage.DomainClass);
if (!_existingExtensionMethods.Add((capTypeSyntax.ToString(), methodName.ToString())))
{
continue;
}
var pathDecl = FieldDeclaration(
VariableDeclaration(
IdentifierName(nameof(Capnp.Rpc.MemberAccessPath)))
.AddVariables(
VariableDeclarator(
accessPath.Identifier)
.WithInitializer(
EqualsValueClause(
ObjectCreationExpression(
IdentifierName(nameof(Capnp.Rpc.MemberAccessPath)))
.AddArgumentListArguments(
path.Select(
f => Argument(
LiteralExpression(SyntaxKind.NumericLiteralExpression,
Literal(f.Offset)))).ToArray())))))
.AddModifiers(Static, Readonly);
var methodDecl = MethodDeclaration(
capTypeSyntax,
methodName.Identifier)
.AddModifiers(Public, Static)
.AddParameterListParameters(
Parameter(
_names.TaskParameter.Identifier)
.AddModifiers(This)
.WithType(TransformReturnType(method)))
.AddBodyStatements(
ReturnStatement(
CastExpression(
capTypeSyntax,
InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
IdentifierName(nameof(Capnp.Rpc.CapabilityReflection)),
GenericName(
Identifier(nameof(Capnp.Rpc.CapabilityReflection.CreateProxy)))
.AddTypeArgumentListArguments(
capTypeSyntax)))
.AddArgumentListArguments(
Argument(
InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
IdentifierName(nameof(Capnp.Rpc.Impatient)),
IdentifierName(nameof(Capnp.Rpc.Impatient.GetAnswer))))
.AddArgumentListArguments(
Argument(
_names.TaskParameter.IdentifierName)),
IdentifierName(nameof(Capnp.Rpc.IPromisedAnswer.Access))))
.AddArgumentListArguments(
Argument(
accessPath.IdentifierName)))))));
classDecl = classDecl.AddMembers(pathDecl, methodDecl);
}
}
return classDecl;
}
}
}

View File

@ -1,35 +0,0 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
using System.Collections.Generic;
using System.Text;
namespace CapnpC.Generator
{
class Name
{
readonly string _name;
public Name(string name)
{
_name = name ?? throw new ArgumentNullException(nameof(name));
IdentifierName = SyntaxFactory.IdentifierName(_name);
Identifier = SyntaxFactory.Identifier(_name);
VariableDeclarator = SyntaxFactory.VariableDeclarator(_name);
}
public IdentifierNameSyntax IdentifierName { get; }
public SyntaxToken Identifier { get; }
public VariableDeclaratorSyntax VariableDeclarator { get; }
public override string ToString() => _name;
public override bool Equals(object obj)
{
return obj is Name other && _name == other._name;
}
public override int GetHashCode() => _name.GetHashCode();
}
}

View File

@ -1,715 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CapnpC.Model;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static CapnpC.Generator.SyntaxHelpers;
namespace CapnpC.Generator
{
class ReaderSnippetGen
{
readonly GenNames _names;
public ReaderSnippetGen(GenNames names)
{
_names = names;
}
MemberDeclarationSyntax MakeReaderImplicitConversionOperator1()
{
return ConversionOperatorDeclaration(
Token(SyntaxKind.ImplicitKeyword),
IdentifierName(nameof(Capnp.DeserializerState)))
.WithModifiers(
TokenList(
new[]{
Token(SyntaxKind.PublicKeyword),
Token(SyntaxKind.StaticKeyword)}))
.WithParameterList(
ParameterList(
SingletonSeparatedList<ParameterSyntax>(
Parameter(_names.ReaderParameter.Identifier)
.WithType(_names.ReaderStruct.IdentifierName))))
.WithExpressionBody(
ArrowExpressionClause(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
_names.ReaderParameter.IdentifierName,
_names.ReaderContextField.IdentifierName)))
.WithSemicolonToken(
Token(SyntaxKind.SemicolonToken));
}
MemberDeclarationSyntax MakeReaderImplicitConversionOperator2()
{
return ConversionOperatorDeclaration(
Token(SyntaxKind.ImplicitKeyword),
_names.ReaderStruct.IdentifierName)
.WithModifiers(
TokenList(
new[] {
Token(SyntaxKind.PublicKeyword),
Token(SyntaxKind.StaticKeyword) }))
.WithParameterList(
ParameterList(
SingletonSeparatedList<ParameterSyntax>(
Parameter(_names.ReaderContextField.Identifier)
.WithType(Type<Capnp.DeserializerState>()))))
.WithExpressionBody(
ArrowExpressionClause(
ObjectCreationExpression(_names.ReaderStruct.IdentifierName)
.WithArgumentList(
ArgumentList(
SingletonSeparatedList<ArgumentSyntax>(
Argument(_names.ReaderContextField.IdentifierName))))))
.WithSemicolonToken(
Token(SyntaxKind.SemicolonToken));
}
MemberDeclarationSyntax MakeReaderCreateMethod()
{
return MethodDeclaration(_names.ReaderStruct.IdentifierName, _names.ReaderCreateMethod.Identifier)
.AddModifiers(Public, Static)
.WithParameterList(
ParameterList(
SingletonSeparatedList(
Parameter(_names.ContextParameter.Identifier)
.WithType(
Type<Capnp.DeserializerState>()))))
.WithExpressionBody(
ArrowExpressionClause(
ObjectCreationExpression(_names.ReaderStruct.IdentifierName)
.AddArgumentListArguments(Argument(_names.ContextParameter.IdentifierName))))
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken));
}
IEnumerable<MemberDeclarationSyntax> MakeReaderStructMembers()
{
yield return FieldDeclaration(
VariableDeclaration(
Type<Capnp.DeserializerState>())
.AddVariables(_names.ReaderContextField.VariableDeclarator))
.AddModifiers(Readonly);
yield return ConstructorDeclaration(_names.ReaderStruct.Identifier)
.AddModifiers(Public)
.WithParameterList(
ParameterList(
SingletonSeparatedList(
Parameter(_names.ContextParameter.Identifier)
.WithType(Type<Capnp.DeserializerState>()))))
.WithBody(
Block(
SingletonList<StatementSyntax>(
ExpressionStatement(
AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
ThisExpression(),
_names.ReaderContextField.IdentifierName),
_names.ContextParameter.IdentifierName)))));
yield return MakeReaderCreateMethod();
yield return MakeReaderImplicitConversionOperator1();
yield return MakeReaderImplicitConversionOperator2();
}
IEnumerable<MemberDeclarationSyntax> MakeGroupReaderStructMembers()
{
yield return FieldDeclaration(
VariableDeclaration(
Type<Capnp.DeserializerState>())
.AddVariables(_names.ReaderContextField.VariableDeclarator))
.AddModifiers(Readonly);
yield return ConstructorDeclaration(_names.ReaderStruct.Identifier)
.AddModifiers(Public)
.WithParameterList(
ParameterList(
SingletonSeparatedList(Parameter(_names.GroupReaderContextArg.Identifier)
.WithType(Type<Capnp.DeserializerState>()))))
.WithBody(
Block(
SingletonList<StatementSyntax>(
ExpressionStatement(
AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
ThisExpression(),
_names.ReaderContextField.IdentifierName),
_names.GroupReaderContextArg.IdentifierName)))));
yield return MakeReaderCreateMethod();
yield return MakeReaderImplicitConversionOperator1();
yield return MakeReaderImplicitConversionOperator2();
}
PropertyDeclarationSyntax MakeReaderProperty(TypeSyntax type, string name, ExpressionSyntax right, bool cond)
{
if (cond)
{
right = ConditionalExpression(
BinaryExpression(
SyntaxKind.EqualsExpression,
_names.UnionDiscriminatorProp.IdentifierName,
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
_names.UnionDiscriminatorEnum.IdentifierName,
IdentifierName(name))),
right,
LiteralExpression(
SyntaxKind.DefaultLiteralExpression,
Token(SyntaxKind.DefaultKeyword)));
}
return PropertyDeclaration(type, name)
.AddModifiers(Public)
.WithExpressionBody(ArrowExpressionClause(right))
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken));
}
static Func<ExpressionSyntax, ExpressionSyntax> MakeCastFunc(TypeSyntax type) =>
x => CastExpression(type, x);
static Func<ExpressionSyntax, ExpressionSyntax> MakeListCastFunc(string castName, Field field)
{
// Insight: List may have complex default values (e.g. [true, false, false, true] as a
// valid default value for a list of bools. This does not yet fit the author's mindset.
//if (field.DefaultValueIsExplicit)
//{
// return x => InvocationExpression(
// MemberAccessExpression(
// SyntaxKind.SimpleMemberAccessExpression,
// x,
// IdentifierName(castName))
// )
// .AddArgumentListArguments(
// Argument(ValueOf(field.DefaultValue)));
//}
//else
{
return x => InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
x,
IdentifierName(castName))
);
}
}
static Func<ExpressionSyntax, ExpressionSyntax> MakeListCastFuncWithCons(
string castName, ExpressionSyntax cons)
{
return x => InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
x,
IdentifierName(castName))
).AddArgumentListArguments(Argument(cons));
}
static Func<ExpressionSyntax, ExpressionSyntax> MakeGenericListCastFunc(string castName, TypeSyntax genericArgument)
{
return x => InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
x,
GenericName(castName).AddTypeArgumentListArguments(genericArgument))
);
}
PropertyDeclarationSyntax MakeReadProperty(TypeSyntax type, string name, SimpleNameSyntax readName,
object indexOrBitOffset, ExpressionSyntax secondArg,
Func<ExpressionSyntax, ExpressionSyntax> cast, bool cond)
{
var right = InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
_names.ReaderContextField.IdentifierName,
readName))
.AddArgumentListArguments(
Argument(ValueOf(indexOrBitOffset)));
if (secondArg != null)
{
right = right.AddArgumentListArguments(Argument(secondArg));
}
ExpressionSyntax expr = right;
if (cast != null)
{
expr = cast(expr);
}
return MakeReaderProperty(type, name, expr, cond);
}
PropertyDeclarationSyntax MakeReadProperty(TypeSyntax type, string name, string readName,
object indexOrBitOffset, ExpressionSyntax secondArg,
Func<ExpressionSyntax, ExpressionSyntax> cast, bool cond)
{
return MakeReadProperty(type, name, IdentifierName(readName), indexOrBitOffset, secondArg, cast, cond);
}
PropertyDeclarationSyntax MakeReadPrimitiveProperty<T>(Field field, string readName)
{
return MakeReadProperty(Type<T>(), _names.GetCodeIdentifier(field).ToString(), readName, field.BitOffset.Value,
ValueOf(field.DefaultValue.ScalarValue), null, field.DiscValue.HasValue);
}
PropertyDeclarationSyntax MakeReadEnumProperty(Field field)
{
var typeSyntax = _names.MakeTypeSyntax(field.Type, field.DeclaringType, TypeUsage.Reader);
return MakeReadProperty(typeSyntax,
_names.GetCodeIdentifier(field).ToString(),
nameof(Capnp.SerializerExtensions.ReadDataUShort), field.BitOffset.Value,
ValueOf(field.DefaultValue.ScalarValue),
x => CastExpression(typeSyntax, x),
field.DiscValue.HasValue);
}
PropertyDeclarationSyntax MakeReadTextProperty(Field field)
{
return MakeReadProperty(Type<string>(), _names.GetCodeIdentifier(field).ToString(),
nameof(Capnp.DeserializerState.ReadText), (int)field.Offset,
ValueOf(field.DefaultValue.ScalarValue), null, field.DiscValue.HasValue);
}
MemberAccessExpressionSyntax MakeReaderCreator(TypeSyntax qtype)
{
return MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
qtype,
_names.ReaderCreateMethod.IdentifierName);
}
PropertyDeclarationSyntax MakeReadStructProperty(Field field)
{
var qtype = _names.MakeTypeSyntax(field.Type, field.DeclaringType, TypeUsage.Reader);
var creator = MakeReaderCreator(qtype);
return MakeReadProperty(qtype, _names.GetCodeIdentifier(field).ToString(),
nameof(Capnp.DeserializerState.ReadStruct), (int)field.Offset,
creator, null, field.DiscValue.HasValue);
}
PropertyDeclarationSyntax MakeReadGroupProperty(Field field)
{
var type = QualifiedName(
_names.MakeTypeName(field.Type.Definition).IdentifierName,
_names.ReaderStruct.IdentifierName);
var right = ObjectCreationExpression(type)
.WithArgumentList(
ArgumentList(
SingletonSeparatedList(
Argument(_names.ReaderContextField.IdentifierName))));
return MakeReaderProperty(type, _names.GetCodeIdentifier(field).ToString(), right, field.DiscValue.HasValue);
}
void MakeReadListPropertyImpl(Model.Type elementType, TypeDefinition scope, ExpressionSyntax context, int depth,
out TypeSyntax listType, out ExpressionSyntax impl)
{
var elementTypeSyntax = _names.MakeTypeSyntax(elementType, scope, TypeUsage.Reader);
listType = GenericName("IReadOnlyList").AddTypeArgumentListArguments(elementTypeSyntax);
if (elementType.Tag == TypeTag.Interface ||
elementType.Tag == TypeTag.CapabilityPointer)
{
if (depth == 0)
{
impl = InvocationExpression(MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
_names.ReaderContextField.IdentifierName,
GenericName(nameof(Capnp.DeserializerState.ReadCapList))
.AddTypeArgumentListArguments(elementTypeSyntax)
)).AddArgumentListArguments(Argument(context));
}
else
{
impl = InvocationExpression(
MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
context,
GenericName(nameof(Capnp.DeserializerState.RequireCapList))
.AddTypeArgumentListArguments(elementTypeSyntax)
));
}
return;
}
if (depth == 0)
{
context = InvocationExpression(MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
_names.ReaderContextField.IdentifierName,
IdentifierName(nameof(Capnp.DeserializerState.ReadList))))
.AddArgumentListArguments(Argument(context));
}
else
{
context = InvocationExpression(
MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
context,
IdentifierName(nameof(Capnp.DeserializerState.RequireList))
));
}
string lambdaParamName = "_" + depth;
var lambdaParam = Parameter(Identifier(lambdaParamName));
var lambdaArg = IdentifierName(lambdaParamName);
string castFuncName;
switch (elementType.Tag)
{
case TypeTag.List:
{
MakeReadListPropertyImpl(
elementType.ElementType,
scope,
lambdaArg,
depth + 1,
out var innerListType,
out var innerImpl);
listType = GenericName("IReadOnlyList").AddTypeArgumentListArguments(innerListType);
impl = InvocationExpression(
MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
context,
IdentifierName(nameof(Capnp.ListDeserializer.Cast))))
.AddArgumentListArguments(
Argument(SimpleLambdaExpression(lambdaParam, innerImpl)));
return;
}
case TypeTag.ListPointer:
{
listType = Type<IReadOnlyList<object>>();
context = InvocationExpression(MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
context,
IdentifierName(nameof(Capnp.DeserializerState.RequireList))
)).AddArgumentListArguments(Argument(lambdaArg));
impl = InvocationExpression(
MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
context,
IdentifierName(nameof(Capnp.ReadOnlyListExtensions.LazyListSelect))))
.AddArgumentListArguments(
Argument(SimpleLambdaExpression(lambdaParam, context)));
return;
}
case TypeTag.Struct:
{
impl = InvocationExpression(
MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
context,
IdentifierName(nameof(Capnp.ListDeserializer.Cast))))
.AddArgumentListArguments(
Argument(MakeReaderCreator(elementTypeSyntax)));
return;
}
case TypeTag.Enum:
{
var cons = SimpleLambdaExpression(
lambdaParam,
CastExpression(elementTypeSyntax, lambdaArg));
impl = InvocationExpression(
MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
context,
IdentifierName(nameof(Capnp.ListDeserializer.CastEnums))))
.AddArgumentListArguments(
Argument(cons));
return;
}
case TypeTag.AnyPointer:
case TypeTag.StructPointer:
{
listType = Type<IReadOnlyList<Capnp.DeserializerState>>();
impl = context;
return;
}
case TypeTag.Void:
{
listType = Type<int>();
impl = MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
context,
IdentifierName(nameof(Capnp.ListDeserializer.Count)));
return;
}
case TypeTag.Data:
castFuncName = nameof(Capnp.ListDeserializer.CastData);
break;
case TypeTag.Text:
castFuncName = nameof(Capnp.ListDeserializer.CastText2);
break;
case TypeTag.Bool:
castFuncName = nameof(Capnp.ListDeserializer.CastBool);
break;
case TypeTag.F32:
castFuncName = nameof(Capnp.ListDeserializer.CastFloat);
break;
case TypeTag.F64:
castFuncName = nameof(Capnp.ListDeserializer.CastDouble);
break;
case TypeTag.S8:
castFuncName = nameof(Capnp.ListDeserializer.CastSByte);
break;
case TypeTag.U8:
castFuncName = nameof(Capnp.ListDeserializer.CastByte);
break;
case TypeTag.S16:
castFuncName = nameof(Capnp.ListDeserializer.CastShort);
break;
case TypeTag.U16:
case TypeTag.AnyEnum:
castFuncName = nameof(Capnp.ListDeserializer.CastUShort);
break;
case TypeTag.S32:
castFuncName = nameof(Capnp.ListDeserializer.CastInt);
break;
case TypeTag.U32:
castFuncName = nameof(Capnp.ListDeserializer.CastUInt);
break;
case TypeTag.S64:
castFuncName = nameof(Capnp.ListDeserializer.CastLong);
break;
case TypeTag.U64:
castFuncName = nameof(Capnp.ListDeserializer.CastULong);
break;
default:
throw new NotImplementedException("Unexpected type tag, don't know how to deal with this");
}
impl = InvocationExpression(MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
context,
IdentifierName(castFuncName)));
}
PropertyDeclarationSyntax MakeReadListProperty(Field field)
{
var elementType = field.Type.ElementType;
var context = ValueOf((int)field.Offset);
MakeReadListPropertyImpl(elementType, field.DeclaringType, context, 0, out var listType, out var impl);
return MakeReaderProperty(listType, _names.GetCodeIdentifier(field).ToString(), impl, field.DiscValue.HasValue);
}
PropertyDeclarationSyntax MakeReadAnyListProperty(Field field)
{
var type = _names.MakeTypeSyntax(field.Type, field.DeclaringType, TypeUsage.Reader);
return MakeReadProperty(type, _names.GetCodeIdentifier(field).ToString(),
nameof(Capnp.DeserializerState.ReadList),
(int)field.Offset, null, x => CastExpression(type, x), field.DiscValue.HasValue);
}
PropertyDeclarationSyntax MakeReadCapProperty(Field field)
{
var type = _names.MakeTypeSyntax(field.Type, field.DeclaringType, TypeUsage.Reader);
var readName = GenericName(nameof(Capnp.DeserializerState.ReadCap))
.AddTypeArgumentListArguments(type);
return MakeReadProperty(type, _names.GetCodeIdentifier(field).ToString(),
readName,
(int)field.Offset, null, null, field.DiscValue.HasValue);
}
PropertyDeclarationSyntax MakeReadAnyCapProperty(Field field)
{
var type = _names.MakeTypeSyntax(field.Type, field.DeclaringType, TypeUsage.Reader);
return MakeReadProperty(type, _names.GetCodeIdentifier(field).ToString(),
nameof(Capnp.DeserializerState.ReadCap),
(int)field.Offset, null, null, field.DiscValue.HasValue);
}
PropertyDeclarationSyntax MakeReadDataProperty(Field field)
{
var context = InvocationExpression(MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
_names.ReaderContextField.IdentifierName,
IdentifierName(nameof(Capnp.DeserializerState.ReadList))))
.AddArgumentListArguments(Argument(ValueOf((int)field.Offset)));
var impl = InvocationExpression(MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
context,
IdentifierName(nameof(Capnp.ListDeserializer.CastByte))));
return MakeReaderProperty(
_names.MakeTypeSyntax(field.Type, field.DeclaringType, TypeUsage.Reader),
_names.GetCodeIdentifier(field).ToString(), impl, field.DiscValue.HasValue);
}
PropertyDeclarationSyntax MakeReadAnyPointerProperty(Field field)
{
var type = IdentifierName(nameof(Capnp.DeserializerState));
return MakeReadProperty(type, _names.GetCodeIdentifier(field).ToString(),
nameof(Capnp.DeserializerState.StructReadPointer),
(int)field.Offset, null, null, field.DiscValue.HasValue);
}
PropertyDeclarationSyntax MakeReaderUnionSelector(TypeDefinition def)
{
var type = _names.UnionDiscriminatorEnum.IdentifierName;
return MakeReadProperty(
type,
_names.UnionDiscriminatorProp.ToString(),
nameof(Capnp.SerializerExtensions.ReadDataUShort),
def.UnionInfo.TagOffset,
ValueOf(default(ushort)),
MakeCastFunc(type), false);
}
PropertyDeclarationSyntax MakeReaderFieldProperty(Field field)
{
switch (field.Type.Tag)
{
case TypeTag.Bool:
return MakeReadPrimitiveProperty<bool>(field,
nameof(Capnp.SerializerExtensions.ReadDataBool));
case TypeTag.S8:
return MakeReadPrimitiveProperty<sbyte>(field,
nameof(Capnp.SerializerExtensions.ReadDataSByte));
case TypeTag.U8:
return MakeReadPrimitiveProperty<byte>(field,
nameof(Capnp.SerializerExtensions.ReadDataByte));
case TypeTag.S16:
return MakeReadPrimitiveProperty<short>(field,
nameof(Capnp.SerializerExtensions.ReadDataShort));
case TypeTag.U16:
return MakeReadPrimitiveProperty<ushort>(field,
nameof(Capnp.SerializerExtensions.ReadDataUShort));
case TypeTag.S32:
return MakeReadPrimitiveProperty<int>(field,
nameof(Capnp.SerializerExtensions.ReadDataInt));
case TypeTag.U32:
return MakeReadPrimitiveProperty<uint>(field,
nameof(Capnp.SerializerExtensions.ReadDataUInt));
case TypeTag.S64:
return MakeReadPrimitiveProperty<long>(field,
nameof(Capnp.SerializerExtensions.ReadDataLong));
case TypeTag.U64:
return MakeReadPrimitiveProperty<ulong>(field,
nameof(Capnp.SerializerExtensions.ReadDataULong));
case TypeTag.F32:
return MakeReadPrimitiveProperty<float>(field,
nameof(Capnp.SerializerExtensions.ReadDataFloat));
case TypeTag.F64:
return MakeReadPrimitiveProperty<double>(field,
nameof(Capnp.SerializerExtensions.ReadDataDouble));
case TypeTag.Enum:
return MakeReadEnumProperty(field);
case TypeTag.Text:
return MakeReadTextProperty(field);
case TypeTag.Struct:
return MakeReadStructProperty(field);
case TypeTag.Group:
return MakeReadGroupProperty(field);
case TypeTag.List:
return MakeReadListProperty(field);
case TypeTag.Interface:
return MakeReadCapProperty(field);
case TypeTag.CapabilityPointer:
return MakeReadAnyCapProperty(field);
case TypeTag.ListPointer:
return MakeReadAnyListProperty(field);
case TypeTag.AnyPointer:
case TypeTag.StructPointer:
return MakeReadAnyPointerProperty(field);
case TypeTag.Data:
return MakeReadDataProperty(field);
default:
return null;
}
}
public StructDeclarationSyntax MakeReaderStruct(TypeDefinition def)
{
var readerDecl = StructDeclaration(_names.ReaderStruct.ToString()).AddModifiers(Public);
var members = def.Tag == TypeTag.Group ?
MakeGroupReaderStructMembers() :
MakeReaderStructMembers();
readerDecl = readerDecl.AddMembers(members.ToArray());
if (def.UnionInfo != null)
{
readerDecl = readerDecl.AddMembers(MakeReaderUnionSelector(def));
}
foreach (var field in def.Fields)
{
var propDecl = MakeReaderFieldProperty(field);
if (propDecl != null)
{
readerDecl = readerDecl.AddMembers(propDecl);
}
}
return readerDecl;
}
}
}

View File

@ -1,12 +0,0 @@
using Capnp;
using System.Collections.Generic;
using System.Text;
namespace CapnpC.Generator
{
class SerializerStateWorder: SerializerState
{
public const string LinkName = nameof(SerializerStateWorder.Link);
public const string SetStructName = nameof(SerializerStateWorder.SetStruct);
}
}

View File

@ -1,12 +0,0 @@
using System;
namespace CapnpC.Generator
{
class SkeletonWorder : Capnp.Rpc.Skeleton<object>
{
public override ulong InterfaceId => throw new NotImplementedException();
public const string SetMethodTableName = nameof(SkeletonWorder.SetMethodTable);
public const string ImplName = nameof(SkeletonWorder.Impl);
}
}

View File

@ -1,128 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
namespace CapnpC.Generator
{
static class SyntaxHelpers
{
public static string MakeCamel(string name) => $"{char.ToUpperInvariant(name[0])}{name.Substring(1)}";
public static string MakeAllLower(string name) => $"@{name}";
public static readonly SyntaxToken Async = Token(SyntaxKind.AsyncKeyword);
public static readonly SyntaxToken Public = Token(SyntaxKind.PublicKeyword);
public static readonly SyntaxToken Private = Token(SyntaxKind.PrivateKeyword);
public static readonly SyntaxToken Readonly = Token(SyntaxKind.ReadOnlyKeyword);
public static readonly SyntaxToken Static = Token(SyntaxKind.StaticKeyword);
public static readonly SyntaxToken Override = Token(SyntaxKind.OverrideKeyword);
public static readonly SyntaxToken Partial = Token(SyntaxKind.PartialKeyword);
public static readonly SyntaxToken This = Token(SyntaxKind.ThisKeyword);
public static TypeSyntax Type(Type type)
{
switch (0)
{
case 0 when type == typeof(bool):
return PredefinedType(Token(SyntaxKind.BoolKeyword));
case 0 when type == typeof(sbyte):
return PredefinedType(Token(SyntaxKind.SByteKeyword));
case 0 when type == typeof(byte):
return PredefinedType(Token(SyntaxKind.ByteKeyword));
case 0 when type == typeof(short):
return PredefinedType(Token(SyntaxKind.ShortKeyword));
case 0 when type == typeof(ushort):
return PredefinedType(Token(SyntaxKind.UShortKeyword));
case 0 when type == typeof(int):
return PredefinedType(Token(SyntaxKind.IntKeyword));
case 0 when type == typeof(uint):
return PredefinedType(Token(SyntaxKind.UIntKeyword));
case 0 when type == typeof(long):
return PredefinedType(Token(SyntaxKind.LongKeyword));
case 0 when type == typeof(ulong):
return PredefinedType(Token(SyntaxKind.ULongKeyword));
case 0 when type == typeof(float):
return PredefinedType(Token(SyntaxKind.FloatKeyword));
case 0 when type == typeof(double):
return PredefinedType(Token(SyntaxKind.DoubleKeyword));
case 0 when type == typeof(string):
return PredefinedType(Token(SyntaxKind.StringKeyword));
case 0 when type == typeof(object):
return PredefinedType(Token(SyntaxKind.ObjectKeyword));
case 0 when type.IsGenericType:
return GenericName(type.Name.Substring(0, type.Name.IndexOf('`')))
.AddTypeArgumentListArguments(type.GetGenericArguments().Select(Type).ToArray());
default:
return ParseTypeName(type.Name);
}
}
public static TypeSyntax Type<T>() => Type(typeof(T));
public static ExpressionSyntax ValueOf(object value)
{
switch (value)
{
case bool x:
return LiteralExpression(x ? SyntaxKind.TrueLiteralExpression : SyntaxKind.FalseLiteralExpression);
case sbyte x:
return CastExpression(Type<sbyte>(), LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(x)));
case byte x:
return CastExpression(Type<byte>(), LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(x)));
case short x:
return CastExpression(Type<short>(), LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(x)));
case ushort x:
return CastExpression(Type<ushort>(), LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(x)));
case int x:
return LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(x));
case uint x:
return LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(x));
case long x:
return LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(x));
case ulong x:
return LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(x));
case float x:
return LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(x));
case double x:
return LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(x));
case string x:
return LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(x));
case null:
return LiteralExpression(SyntaxKind.NullLiteralExpression);
default:
throw new NotImplementedException();
}
}
}
}

View File

@ -1,406 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CapnpC.Model;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static CapnpC.Generator.SyntaxHelpers;
namespace CapnpC.Generator
{
class WriterSnippetGen
{
readonly GenNames _names;
public WriterSnippetGen(GenNames names)
{
_names = names;
}
IEnumerable<MemberDeclarationSyntax> MakeWriterStructMembers(TypeDefinition structType)
{
yield return ConstructorDeclaration(_names.WriterStruct.Identifier)
.AddModifiers(Public)
.WithBody(
Block(
ExpressionStatement(
InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
ThisExpression(),
IdentifierName(SerializerStateWorder.SetStructName)))
.AddArgumentListArguments(
Argument(
LiteralExpression(
SyntaxKind.NumericLiteralExpression,
Literal(structType.StructDataWordCount))),
Argument(
LiteralExpression(
SyntaxKind.NumericLiteralExpression,
Literal(structType.StructPointerCount)))))));
}
IEnumerable<MemberDeclarationSyntax> MakeGroupWriterStructMembers()
{
yield return ConstructorDeclaration(_names.WriterStruct.Identifier)
.AddModifiers(Public)
.WithBody(Block());
}
PropertyDeclarationSyntax MakeWriterProperty(
TypeSyntax type,
string name,
ExpressionSyntax getter,
ExpressionSyntax setter,
bool cast,
bool cond)
{
if (cast)
{
getter = CastExpression(type, getter);
}
if (cond)
{
getter = ConditionalExpression(
BinaryExpression(
SyntaxKind.EqualsExpression,
_names.UnionDiscriminatorProp.IdentifierName,
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
_names.UnionDiscriminatorEnum.IdentifierName,
IdentifierName(name))),
getter,
LiteralExpression(
SyntaxKind.DefaultLiteralExpression,
Token(SyntaxKind.DefaultKeyword)));
}
var accessors = new AccessorDeclarationSyntax[setter != null ? 2 : 1];
accessors[0] = AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
.WithExpressionBody(ArrowExpressionClause(getter))
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken));
if (setter != null)
{
accessors[1] = AccessorDeclaration(SyntaxKind.SetAccessorDeclaration)
.WithExpressionBody(ArrowExpressionClause(setter))
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken));
}
return PropertyDeclaration(type, name)
.AddModifiers(Public)
.AddAccessorListAccessors(accessors);
}
ExpressionSyntax MakePointerSyntax(TypeSyntax type, object index) =>
InvocationExpression(
GenericName(nameof(Capnp.SerializerState.BuildPointer))
.AddTypeArgumentListArguments(type))
.AddArgumentListArguments(
Argument(ValueOf(index)));
ExpressionSyntax MakeReadCapSyntax(TypeSyntax type, object index) =>
InvocationExpression(
GenericName(nameof(Capnp.SerializerState.ReadCap))
.AddTypeArgumentListArguments(type))
.AddArgumentListArguments(
Argument(ValueOf(index)));
ExpressionSyntax MakeTypedPointerSyntax(object index, TypeSyntax type) =>
InvocationExpression(
GenericName(nameof(Capnp.SerializerState.BuildPointer))
.AddTypeArgumentListArguments(type))
.AddArgumentListArguments(
Argument(ValueOf(index)));
ExpressionSyntax MakeLinkSyntax(object index) =>
InvocationExpression(
IdentifierName(SerializerStateWorder.LinkName))
.AddArgumentListArguments(
Argument(ValueOf(index)),
Argument(IdentifierName("value")));
ExpressionSyntax MakeLinkObjectSyntax(object index) =>
InvocationExpression(
IdentifierName(nameof(Capnp.SerializerState.LinkObject)))
.AddArgumentListArguments(
Argument(ValueOf(index)),
Argument(IdentifierName("value")));
PropertyDeclarationSyntax MakePointerProperty(TypeSyntax type, string name, object index, bool cast, bool cond)
{
ExpressionSyntax getter = MakePointerSyntax(type, index);
ExpressionSyntax setter = MakeLinkSyntax(index);
return MakeWriterProperty(type, name, getter, setter, cast, cond);
}
PropertyDeclarationSyntax MakePointerAsStructProperty(
TypeSyntax type, string name, object index,
bool cast, bool cond)
{
ExpressionSyntax getter = MakeTypedPointerSyntax(index, type);
ExpressionSyntax setter = MakeLinkSyntax(index);
return MakeWriterProperty(type, name, getter, setter, cast, cond);
}
PropertyDeclarationSyntax MakeProperty(
TypeSyntax outerType,
TypeSyntax innerType,
string name,
string readName,
string writeName,
object indexOrBitOffset,
ExpressionSyntax secondArg,
bool cast,
bool cond,
bool pasd)
{
var getter = InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
ThisExpression(),
IdentifierName(readName)))
.AddArgumentListArguments(
Argument(ValueOf(indexOrBitOffset)),
Argument(secondArg));
ExpressionSyntax value = IdentifierName("value");
if (cast)
{
value = CastExpression(innerType, value);
}
var setter = InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
ThisExpression(),
IdentifierName(writeName)))
.AddArgumentListArguments(
Argument(ValueOf(indexOrBitOffset)),
Argument(value),
Argument(secondArg));
if (pasd)
{
setter.AddArgumentListArguments(Argument(secondArg));
}
return MakeWriterProperty(outerType, name, getter, setter, cast, cond);
}
PropertyDeclarationSyntax MakePrimitiveProperty<T>(Field field, string readName)
{
return MakeProperty(Type<T>(), null, _names.GetCodeIdentifier(field).ToString(),
readName,
nameof(Capnp.SerializerExtensions.WriteData),
field.BitOffset.Value,
ValueOf(field.DefaultValue.ScalarValue),
false,
field.DiscValue.HasValue,
true);
}
PropertyDeclarationSyntax MakeEnumProperty(Field field, string readName)
{
return MakeProperty(_names.MakeTypeSyntax(field.Type, field.DeclaringType, TypeUsage.NotRelevant), Type<ushort>(),
_names.GetCodeIdentifier(field).ToString(),
readName,
nameof(Capnp.SerializerExtensions.WriteData),
field.BitOffset.Value,
ValueOf(field.DefaultValue.ScalarValue),
true,
field.DiscValue.HasValue,
true);
}
PropertyDeclarationSyntax MakeTextProperty(Field field)
{
return MakeProperty(Type<string>(), null,
_names.GetCodeIdentifier(field).ToString(),
nameof(Capnp.SerializerState.ReadText),
nameof(Capnp.SerializerState.WriteText),
(int)field.Offset,
ValueOf(field.DefaultValue.ScalarValue),
false,
field.DiscValue.HasValue,
false);
}
PropertyDeclarationSyntax MakeStructProperty(Field field)
{
var qtype = _names.MakeTypeSyntax(field.Type, field.DeclaringType, TypeUsage.Writer);
return MakePointerAsStructProperty(qtype, _names.GetCodeIdentifier(field).ToString(),
(int)field.Offset, false, field.DiscValue.HasValue);
}
PropertyDeclarationSyntax MakeGroupProperty(Field field)
{
var type = QualifiedName(
_names.MakeTypeName(field.Type.Definition).IdentifierName,
_names.WriterStruct.IdentifierName);
var getter = InvocationExpression(
GenericName(nameof(Capnp.SerializerState.Rewrap))
.AddTypeArgumentListArguments(type));
return MakeWriterProperty(type, _names.GetCodeIdentifier(field).ToString(), getter, null, false, field.DiscValue.HasValue);
}
PropertyDeclarationSyntax MakeListProperty(Field field)
{
var qtype = _names.MakeTypeSyntax(field.Type, field.DeclaringType, TypeUsage.Writer);
return MakePointerProperty(qtype, _names.GetCodeIdentifier(field).ToString(),
(int)field.Offset, false, field.DiscValue.HasValue);
}
PropertyDeclarationSyntax MakePointerProperty(Field field)
{
var type = IdentifierName(nameof(Capnp.DynamicSerializerState));
return MakePointerProperty(type, _names.GetCodeIdentifier(field).ToString(), (int)field.Offset, false, field.DiscValue.HasValue);
}
PropertyDeclarationSyntax MakeCapProperty(Field field)
{
var type = _names.MakeTypeSyntax(field.Type, field.DeclaringType, TypeUsage.Writer);
int index = (int)field.Offset;
string name = _names.GetCodeIdentifier(field).ToString();
ExpressionSyntax getter = MakeReadCapSyntax(type, index);
ExpressionSyntax setter = MakeLinkObjectSyntax(index);
return MakeWriterProperty(type, name, getter, setter, false, field.DiscValue.HasValue);
}
PropertyDeclarationSyntax MakeWriterUnionSelector(TypeDefinition def)
{
return MakeProperty(
_names.UnionDiscriminatorEnum.IdentifierName,
Type<ushort>(),
_names.UnionDiscriminatorProp.ToString(),
nameof(Capnp.SerializerExtensions.ReadDataUShort),
nameof(Capnp.SerializerExtensions.WriteData),
def.UnionInfo.TagOffset,
ValueOf(default(ushort)),
true, false, true);
}
PropertyDeclarationSyntax MakeWriterFieldProperty(Field field)
{
switch (field.Type.Tag)
{
case TypeTag.Bool:
return MakePrimitiveProperty<bool>(field,
nameof(Capnp.SerializerExtensions.ReadDataBool));
case TypeTag.S8:
return MakePrimitiveProperty<sbyte>(field,
nameof(Capnp.SerializerExtensions.ReadDataSByte));
case TypeTag.U8:
return MakePrimitiveProperty<byte>(field,
nameof(Capnp.SerializerExtensions.ReadDataByte));
case TypeTag.S16:
return MakePrimitiveProperty<short>(field,
nameof(Capnp.SerializerExtensions.ReadDataShort));
case TypeTag.U16:
return MakePrimitiveProperty<ushort>(field,
nameof(Capnp.SerializerExtensions.ReadDataUShort));
case TypeTag.S32:
return MakePrimitiveProperty<int>(field,
nameof(Capnp.SerializerExtensions.ReadDataInt));
case TypeTag.U32:
return MakePrimitiveProperty<uint>(field,
nameof(Capnp.SerializerExtensions.ReadDataUInt));
case TypeTag.S64:
return MakePrimitiveProperty<long>(field,
nameof(Capnp.SerializerExtensions.ReadDataLong));
case TypeTag.U64:
return MakePrimitiveProperty<ulong>(field,
nameof(Capnp.SerializerExtensions.ReadDataULong));
case TypeTag.F32:
return MakePrimitiveProperty<float>(field,
nameof(Capnp.SerializerExtensions.ReadDataFloat));
case TypeTag.F64:
return MakePrimitiveProperty<double>(field,
nameof(Capnp.SerializerExtensions.ReadDataDouble));
case TypeTag.Enum:
return MakeEnumProperty(field, nameof(Capnp.SerializerExtensions.ReadDataUShort));
case TypeTag.Text:
return MakeTextProperty(field);
case TypeTag.Struct:
return MakeStructProperty(field);
case TypeTag.Group:
return MakeGroupProperty(field);
case TypeTag.List:
case TypeTag.Data:
return MakeListProperty(field);
case TypeTag.AnyPointer:
case TypeTag.StructPointer:
case TypeTag.ListPointer:
return MakePointerProperty(field);
case TypeTag.CapabilityPointer:
case TypeTag.Interface:
return MakeCapProperty(field);
default:
return null;
}
}
public ClassDeclarationSyntax MakeWriterStruct(TypeDefinition def)
{
var WriterDecl = ClassDeclaration(_names.WriterStruct.ToString())
.AddModifiers(Public)
.AddBaseListTypes(
SimpleBaseType(IdentifierName(nameof(Capnp.SerializerState))));
var members = def.Tag == TypeTag.Group ?
MakeGroupWriterStructMembers() :
MakeWriterStructMembers(def);
WriterDecl = WriterDecl.AddMembers(members.ToArray());
if (def.UnionInfo != null)
{
WriterDecl = WriterDecl.AddMembers(MakeWriterUnionSelector(def));
}
foreach (var field in def.Fields)
{
var propDecl = MakeWriterFieldProperty(field);
if (propDecl != null)
{
WriterDecl = WriterDecl.AddMembers(propDecl);
}
}
return WriterDecl;
}
}
}

View File

@ -1,45 +0,0 @@
using System.Collections.Generic;
namespace CapnpC.Model
{
abstract class AbstractType
{
public TypeTag Tag { get; set; }
protected List<Field> Fields { get; } = new List<Field>();
public uint? FixedBitWidth
{
get
{
switch (Tag)
{
case TypeTag.Bool:
return 1;
case TypeTag.U8:
case TypeTag.S8:
return 8;
case TypeTag.U16:
case TypeTag.S16:
case TypeTag.Enum:
case TypeTag.AnyEnum:
return 16;
case TypeTag.U32:
case TypeTag.S32:
case TypeTag.F32:
return 32;
case TypeTag.U64:
case TypeTag.S64:
case TypeTag.F64:
return 64;
default:
return null;
}
}
}
}
}

View File

@ -1,23 +0,0 @@
using System.Diagnostics;
namespace CapnpC.Model
{
class Annotation : IDefinition
{
public ulong Id { get; }
public bool IsGenerated { get; }
public TypeTag Tag { get => TypeTag.Annotation; }
public IHasNestedDefinitions DeclaringElement { get; }
public Type Type { get; set; }
public Annotation(ulong id, IHasNestedDefinitions parent)
{
Trace.Assert(parent != null);
Id = id;
IsGenerated = (parent as IDefinition).IsGenerated;
DeclaringElement = parent;
parent.NestedDefinitions.Add(this);
}
}
}

View File

@ -1,23 +0,0 @@
using System.Diagnostics;
namespace CapnpC.Model
{
class Constant : IDefinition
{
public ulong Id { get; }
public bool IsGenerated { get; }
public TypeTag Tag { get => TypeTag.Const; }
public IHasNestedDefinitions DeclaringElement { get; }
public Value Value { get; set; }
public Constant(ulong id, IHasNestedDefinitions parent)
{
Trace.Assert(parent != null);
Id = id;
IsGenerated = (parent as IDefinition).IsGenerated;
DeclaringElement = parent;
parent.NestedDefinitions.Add(this);
}
}
}

View File

@ -1,67 +0,0 @@
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, bool isGenerated)
=> CreateId<GenFile>(id, () => new GenFile(id, isGenerated));
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

@ -1,14 +0,0 @@
namespace CapnpC.Model
{
class Enumerant
{
string _literal;
public TypeDefinition TypeDefinition { get; set; }
public string Literal {
get => _literal;
set => _literal = IdentifierRenamer.ToNonKeyword(value);
}
public ushort? Ordinal { get; set; }
public int CodeOrder { get; set; }
}
}

View File

@ -1,52 +0,0 @@
namespace CapnpC.Model
{
class Field
{
public TypeDefinition DeclaringType { get; set; }
public Field Parent { get; set; }
public string Name { get; set; }
public Type Type { get; set; }
public Value DefaultValue { get; set; }
public bool DefaultValueIsExplicit { get; set; }
public ushort? DiscValue { get; set; }
public uint Offset { get; set; }
public int CodeOrder { get; set; }
public ulong? BitOffset => (ulong)Offset * Type?.FixedBitWidth;
public Field Clone()
{
var field = new Field()
{
DeclaringType = DeclaringType,
Parent = Parent,
Name = Name,
Type = Type,
DefaultValue = DefaultValue,
DefaultValueIsExplicit = DefaultValueIsExplicit,
DiscValue = DiscValue,
Offset = Offset,
CodeOrder = CodeOrder,
};
field.InheritFreeGenericParameters();
return field;
}
public void InheritFreeGenericParameters()
{
Type.InheritFreeParameters(DeclaringType);
}
public override bool Equals(object obj)
{
return obj is Field other &&
DeclaringType == other.DeclaringType &&
Name == other.Name;
}
public override int GetHashCode()
{
return (DeclaringType, Name).GetHashCode();
}
}
}

View File

@ -1,25 +0,0 @@
using System.Collections.Generic;
namespace CapnpC.Model
{
class GenFile: IDefinition, IHasNestedDefinitions
{
public ulong Id { get; }
public bool IsGenerated { get; }
public TypeTag Tag { get => TypeTag.File; }
public IHasNestedDefinitions DeclaringElement { get => null; }
public string Name { get; set; }
public string[] Namespace { get; set; }
public IEnumerable<TypeDefinition> NestedTypes { get => this.GetNestedTypes(); }
public ICollection<IDefinition> NestedDefinitions { get; } = new List<IDefinition>();
public ICollection<Constant> Constants { get; } = new List<Constant>();
public GenFile(ulong id, bool isGenerated)
{
Id = id;
IsGenerated = isGenerated;
}
}
}

View File

@ -1,26 +0,0 @@
namespace CapnpC.Model
{
class GenericParameter
{
public IHasGenericParameters DeclaringEntity { get; set; }
public int Index { get; set; }
public string Name => DeclaringEntity.GenericParameters[Index];
public override bool Equals(object obj)
{
// Instead of equality by Name, we could instead take (DeclaringEntity, Index), but there is a caveat:
// Since methods can also own generic parameters, we have different classes of declaring entities involved.
// Both the method will define generic parameters, and the Cap'n'p-generated params/result structs.
// Therefore we end in two GenericParameter instances, one with the Method as declaring entity, the
// other one with the params/result type definition as declaring entity. They are semantically the same,
// and the easy way to match them is by Name. Equality by Name is the only working choice, even though
// it feels a little less reboust than by matching declaring entity + parameter position.
return obj is GenericParameter other && Name == other.Name;
}
public override int GetHashCode()
{
return Name.GetHashCode();
}
}
}

View File

@ -1,18 +0,0 @@
using System.Collections.Generic;
namespace CapnpC.Model
{
static class HasGenericParameters
{
public static IEnumerable<GenericParameter> GetLocalTypeParameters(this IHasGenericParameters me)
{
for (int i = 0; i < me.GenericParameters.Count; i++)
{
yield return new GenericParameter()
{
DeclaringEntity = me,
Index = i
};
}
}
}
}

View File

@ -1,11 +0,0 @@

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

View File

@ -1,9 +0,0 @@
using System.Collections.Generic;
namespace CapnpC.Model
{
interface IHasGenericParameters
{
List<string> GenericParameters { get; }
}
}

View File

@ -1,18 +0,0 @@
using System.Collections.Generic;
using System.Linq;
namespace CapnpC.Model
{
interface IHasNestedDefinitions
{
IEnumerable<TypeDefinition> NestedTypes { 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

@ -1,22 +0,0 @@
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

@ -1,11 +0,0 @@
using System;
namespace CapnpC.Model
{
class InvalidSchemaException : Exception
{
public InvalidSchemaException(string message) : base(message)
{
}
}
}

View File

@ -1,16 +0,0 @@
using System.Collections.Generic;
namespace CapnpC.Model
{
class Method: IHasGenericParameters
{
public TypeDefinition DeclaringInterface { get; set; }
public int Id { get; set; }
public string Name { get; set; }
public List<Field> Params { get; } = new List<Field>();
public List<Field> Results { get; } = new List<Field>();
public Type ParamsStruct { get; set; }
public Type ResultStruct { get; set; }
public List<string> GenericParameters { get; } = new List<string>();
}
}

View File

@ -1,783 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
namespace CapnpC.Model
{
class SchemaModel
{
readonly Schema.CodeGeneratorRequest.Reader _request;
readonly List<GenFile> _generatedFiles = new List<GenFile>();
readonly DefinitionManager _typeDefMgr = new DefinitionManager();
readonly Dictionary<ulong, Schema.Node.Reader> _id2node = new Dictionary<ulong, Schema.Node.Reader>();
public SchemaModel(Schema.CodeGeneratorRequest.Reader request)
{
_request = request;
}
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)
{
return (Schema.Node.Reader)IdToNode(id, true);
}
void Build()
{
if (_request.Nodes == null || _request.Nodes.Count == 0)
{
throw new InvalidSchemaException("No nodes, nothing to generate");
}
foreach (var node in _request.Nodes)
{
if (_id2node.TryGetValue(node.Id, out var existingNode))
{
throw new InvalidSchemaException($"Node {node.StrId()} \"{node.DisplayName}\" has a duplicate ID, prior node was \"{existingNode.DisplayName}\"");
}
_id2node[node.Id] = node;
}
var requestedFiles = _request.RequestedFiles.ToDictionary(req => req.Id);
BuildPass1(requestedFiles);
BuildPass2(requestedFiles);
}
// First pass: create type definitions for each node.
struct Pass1State
{
public HashSet<ulong> unprocessedNodes;
public IHasNestedDefinitions parent;
}
void BuildPass1(Dictionary<ulong, Schema.CodeGeneratorRequest.RequestedFile.Reader> requestedFiles)
{
Pass1State state = new Pass1State()
{
unprocessedNodes = new HashSet<ulong>(_id2node.Keys)
};
foreach (var node in _id2node.Values.Where(n => n.IsFile))
{
GenFile file;
bool isGenerated = requestedFiles.TryGetValue(node.Id, out var req);
var filename = isGenerated ? req.Filename : node.DisplayName;
file = ProcessFilePass1(node.Id, filename, state, isGenerated);
if (isGenerated)
_generatedFiles.Add(file);
}
if (state.unprocessedNodes.Count != 0)
{
throw new InvalidSchemaException("Unreferenced nodes were present in the schema.");
}
}
GenFile ProcessFilePass1(ulong id, string name, Pass1State state, bool isGenerated)
{
var file = _typeDefMgr.CreateFile(id, isGenerated);
var node = IdToNode(id);
state.parent = null;
file.Namespace = GetNamespaceAnnotation(node);
file.Name = name;
return ProcessNodePass1(id, name, state) as GenFile;
}
IDefinition ProcessNodePass1(ulong id, string name, Pass1State state)
{
bool mustExist = state.parent == null || (state.parent as IDefinition).IsGenerated;
if (!(IdToNode(id, mustExist) 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 a file node {node.StrId()} to be a nested node.");
var file = _typeDefMgr.GetExistingFile(id);
file.Namespace = GetNamespaceAnnotation(node);
file.Name = name;
state.parent = file;
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)
{
Trace.Assert(state.parent != null, $"The {node.GetTypeTag().ToString()} node {node.StrId()} was expected to have a parent.");
var typeDef = _typeDefMgr.CreateTypeDef(id, node.GetTypeTag(), state.parent);
typeDef.Name = name;
state.parent = typeDef;
def = typeDef;
}
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
{
return annotation.Value.Text.Split(new string[1] { "::" }, default);
}
}
return null;
}
// 2nd pass: Generate types based on definitions
struct Pass2State
{
public Method currentMethod;
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(file.Id);
ProcessNestedNodes(node.NestedNodes, state, file.IsGenerated);
}
}
void ProcessNestedNodes(IEnumerable<Schema.Node.NestedNode.Reader> nestedNodes, Pass2State state, bool mustExist)
{
foreach (var nestedNode in nestedNodes)
{
ProcessNode(nestedNode.Id, state, mustExist);
}
}
void ProcessBrand(Schema.Brand.Reader brandReader, Type type, Pass2State state)
{
foreach (var scopeReader in brandReader.Scopes)
{
var whatToBind = ProcessTypeDef(scopeReader.ScopeId, state);
int index = 0;
switch (0)
{
case 0 when scopeReader.IsBind:
foreach (var bindingReader in scopeReader.Bind)
{
var typeParameter = new GenericParameter()
{
DeclaringEntity = whatToBind,
Index = index++
};
switch (0)
{
case 0 when bindingReader.IsType:
type.BindGenericParameter(typeParameter, ProcessType(bindingReader.Type, state));
break;
case 0 when bindingReader.IsUnbound:
type.BindGenericParameter(typeParameter, Types.FromParameter(typeParameter));
break;
}
}
break;
case 0 when scopeReader.IsInherit:
for (index = 0; index < type.DeclaringType.Definition.GenericParameters.Count; index++)
{
var typeParameter = new GenericParameter()
{
DeclaringEntity = whatToBind,
Index = index
};
type.BindGenericParameter(typeParameter, Types.FromParameter(typeParameter));
}
break;
}
}
}
Type ProcessType(Schema.Type.Reader typeReader, Pass2State state)
{
Type result;
switch (0)
{
case 0 when typeReader.IsAnyPointer:
switch (0)
{
case 0 when typeReader.AnyPointer_IsParameter:
return Types.FromParameter(
new GenericParameter()
{
DeclaringEntity = ProcessTypeDef(typeReader.AnyPointer_Parameter_ScopeId, state),
Index = typeReader.AnyPointer_Parameter_ParameterIndex
});
case 0 when typeReader.AnyPointer_IsImplicitMethodParameter:
return Types.FromParameter(
new GenericParameter()
{
DeclaringEntity = state.currentMethod ?? throw new InvalidOperationException("current method not set"),
Index = typeReader.AnyPointer_ImplicitMethodParameter_ParameterIndex
});
case 0 when typeReader.AnyPointer_IsUnconstrained:
switch (0)
{
case 0 when typeReader.AnyPointer_Unconstrained_IsAnyKind:
return Types.AnyPointer;
case 0 when typeReader.AnyPointer_Unconstrained_IsCapability:
return Types.CapabilityPointer;
case 0 when typeReader.AnyPointer_Unconstrained_IsList:
return Types.ListPointer;
case 0 when typeReader.AnyPointer_Unconstrained_IsStruct:
return Types.StructPointer;
default:
throw new NotImplementedException();
}
default:
throw new NotImplementedException();
}
case 0 when typeReader.IsBool:
return Types.Bool;
case 0 when typeReader.IsData:
return Types.Data;
case 0 when typeReader.IsFloat64:
return Types.F64;
case 0 when typeReader.IsEnum:
return Types.FromDefinition(ProcessTypeDef(typeReader.Enum_TypeId, state, TypeTag.Enum));
case 0 when typeReader.IsFloat32:
return Types.F32;
case 0 when typeReader.IsInt16:
return Types.S16;
case 0 when typeReader.IsInt32:
return Types.S32;
case 0 when typeReader.IsInt64:
return Types.S64;
case 0 when typeReader.IsInt8:
return Types.S8;
case 0 when typeReader.IsInterface:
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, state));
case 0 when typeReader.IsStruct:
result = Types.FromDefinition(ProcessTypeDef(typeReader.Struct_TypeId, state, TypeTag.Struct));
ProcessBrand(typeReader.Struct_Brand, result, state);
return result;
case 0 when typeReader.IsText:
return Types.Text;
case 0 when typeReader.IsUInt16:
return Types.U16;
case 0 when typeReader.IsUInt32:
return Types.U32;
case 0 when typeReader.IsUInt64:
return Types.U64;
case 0 when typeReader.IsUInt8:
return Types.U8;
case 0 when typeReader.IsVoid:
return Types.Void;
default:
throw new NotImplementedException();
}
}
Value ProcessValue(Schema.Value.Reader valueReader)
{
var value = new Value();
switch (0)
{
case 0 when valueReader.IsAnyPointer:
value.ScalarValue = valueReader.AnyPointer;
value.Type = Types.AnyPointer;
break;
case 0 when valueReader.IsBool:
value.ScalarValue = valueReader.Bool;
value.Type = Types.Bool;
break;
case 0 when valueReader.IsData:
value.Items.AddRange(valueReader.Data.CastByte().Select(Value.Scalar));
value.Type = Types.Data;
break;
case 0 when valueReader.IsEnum:
value.ScalarValue = valueReader.Enum;
value.Type = Types.AnyEnum;
break;
case 0 when valueReader.IsFloat32:
value.ScalarValue = valueReader.Float32;
value.Type = Types.F32;
break;
case 0 when valueReader.IsFloat64:
value.ScalarValue = valueReader.Float64;
value.Type = Types.F64;
break;
case 0 when valueReader.IsInt16:
value.ScalarValue = valueReader.Int16;
value.Type = Types.S16;
break;
case 0 when valueReader.IsInt32:
value.ScalarValue = valueReader.Int32;
value.Type = Types.S32;
break;
case 0 when valueReader.IsInt64:
value.ScalarValue = valueReader.Int64;
value.Type = Types.S64;
break;
case 0 when valueReader.IsInt8:
value.ScalarValue = valueReader.Int8;
value.Type = Types.S8;
break;
case 0 when valueReader.IsInterface:
value.ScalarValue = null;
value.Type = Types.CapabilityPointer;
break;
case 0 when valueReader.IsList:
value.RawValue = valueReader.List;
value.Type = Types.ListPointer;
break;
case 0 when valueReader.IsStruct:
value.RawValue = valueReader.Struct;
value.Type = Types.StructPointer;
break;
case 0 when valueReader.IsText:
value.ScalarValue = valueReader.Text;
value.Type = Types.Text;
break;
case 0 when valueReader.IsUInt16:
value.ScalarValue = valueReader.UInt16;
value.Type = Types.U16;
break;
case 0 when valueReader.IsUInt32:
value.ScalarValue = valueReader.UInt32;
value.Type = Types.U32;
break;
case 0 when valueReader.IsUInt64:
value.ScalarValue = valueReader.UInt64;
value.Type = Types.U64;
break;
case 0 when valueReader.IsUInt8:
value.ScalarValue = valueReader.UInt8;
value.Type = Types.U8;
break;
case 0 when valueReader.IsVoid:
value.Type = Types.Void;
break;
default:
throw new NotImplementedException();
}
return value;
}
void ProcessFields(Schema.Node.Reader reader, TypeDefinition declaringType, List<Field> fields, Pass2State state)
{
if (reader.Fields == null)
{
return;
}
foreach (var fieldReader in reader.Fields)
{
var field = new Field()
{
DeclaringType = declaringType,
Name = fieldReader.Name,
CodeOrder = fieldReader.CodeOrder
};
if (fieldReader.DiscriminantValue != Schema.Field.Reader.NoDiscriminant)
{
field.DiscValue = fieldReader.DiscriminantValue;
}
switch (0)
{
case 0 when fieldReader.IsGroup:
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, state);
field.DefaultValue.Type = field.Type;
break;
default:
throw new NotImplementedException();
}
field.InheritFreeGenericParameters();
fields.Add(field);
}
}
TypeDefinition ProcessInterfaceOrStructTail(TypeDefinition def, Schema.Node.Reader reader, Pass2State state)
{
def.IsGeneric = reader.IsGeneric;
if (def.IsGeneric)
{
foreach (var paramReader in reader.Parameters)
{
def.GenericParameters.Add(paramReader.Name);
}
}
ProcessNestedNodes(reader.NestedNodes, state, def.File.IsGenerated);
ProcessFields(reader, def, def.Fields, state);
if (reader.IsInterface)
{
foreach (var methodReader in reader.Interface_Methods)
{
var method = new Method()
{
DeclaringInterface = def,
Id = def.Methods.Count,
Name = methodReader.Name
};
foreach (var implicitParameterReader in methodReader.ImplicitParameters)
{
method.GenericParameters.Add(implicitParameterReader.Name);
}
state.currentMethod = method;
def.Methods.Add(method);
var paramNode = IdToNode(methodReader.ParamStructType);
var paramType = ProcessParameterList(paramNode, methodReader.ParamBrand, method.Params, state);
if (paramType != null)
{
paramType.SpecialName = SpecialName.MethodParamsStruct;
paramType.UsingMethod = method;
method.ParamsStruct = Types.FromDefinition(paramType);
}
else
{
method.ParamsStruct = method.Params[0].Type;
}
method.ParamsStruct.InheritFreeParameters(def);
method.ParamsStruct.InheritFreeParameters(method);
var resultNode = IdToNode(methodReader.ResultStructType);
var resultType = ProcessParameterList(resultNode, methodReader.ResultBrand, method.Results, state);
if (resultType != null)
{
resultType.SpecialName = SpecialName.MethodResultStruct;
resultType.UsingMethod = method;
method.ResultStruct = Types.FromDefinition(resultType);
}
else
{
method.ResultStruct = method.Results[0].Type;
}
method.ResultStruct.InheritFreeParameters(def);
method.ResultStruct.InheritFreeParameters(method);
}
state.currentMethod = null;
}
return def;
}
TypeDefinition ProcessStruct(Schema.Node.Reader structReader, TypeDefinition def, Pass2State state)
{
def.StructDataWordCount = structReader.Struct_DataWordCount;
def.StructPointerCount = structReader.Struct_PointerCount;
if (structReader.Struct_DiscriminantCount > 0)
{
def.UnionInfo = new TypeDefinition.DiscriminationInfo(
structReader.Struct_DiscriminantCount,
16u * structReader.Struct_DiscriminantOffset);
}
return ProcessInterfaceOrStructTail(def, structReader, state);
}
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
//# declaration (rather than a single struct parameter type) then a corresponding struct type is
//# auto-generated. Such an auto-generated type will not be listed in the interface's
//# `nestedNodes` and its `scopeId` will be zero -- it is completely detached from the namespace.
//# (Awkwardly, it does of course inherit generic parameters from the method's scope, which makes
//# this a situation where you can't just climb the scope chain to find where a particular
//# generic parameter was introduced. Making the `scopeId` zero was a mistake.)
if (!reader.IsStruct)
{
throw new InvalidSchemaException("Expected a struct");
}
var def = ProcessTypeDef(reader.Id, state, TypeTag.Struct);
if (reader.ScopeId == 0)
{
// Auto-generated => Named parameter list
foreach (var field in def.Fields) list.Add(field);
return def;
}
else
{
// Single, anonymous, struct-typed parameter
var type = Types.FromDefinition(def);
ProcessBrand(brandReader, type, state);
var anon = new Field() { Type = type };
list.Add(anon);
return null;
}
}
TypeDefinition ProcessInterface(Schema.Node.Reader ifaceReader, TypeDefinition def, Pass2State state)
{
foreach (var superClassReader in ifaceReader.Interface_Superclasses)
{
var superClass = ProcessTypeDef(superClassReader.Id, state, TypeTag.Interface);
def.Superclasses.Add(Types.FromDefinition(superClass));
}
return ProcessInterfaceOrStructTail(def, ifaceReader, state);
}
TypeDefinition ProcessEnum(Schema.Node.Reader enumReader, TypeDefinition def, Pass2State state)
{
foreach (var fieldReader in enumReader.Enumerants)
{
var field = new Enumerant()
{
TypeDefinition = def,
Literal = fieldReader.Name,
CodeOrder = fieldReader.CodeOrder
};
if (fieldReader.Ordinal_IsExplicit)
{
field.Ordinal = fieldReader.Ordinal_Explicit;
}
def.Enumerants.Add(field);
}
return def;
}
Constant ProcessConst(Schema.Node.Reader constReader, Constant @const, Pass2State state)
{
var value = ProcessValue(constReader.Const_Value);
value.Type = ProcessType(constReader.Const_Type, state);
@const.Value = value;
return @const;
}
TypeDefinition ProcessTypeDef(ulong id, Pass2State state, TypeTag tag = default)
{
var def = ProcessNode(id, state, true, tag);
var typeDef = def as TypeDefinition;
if (typeDef == 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, bool mustExist, TypeTag tag = default)
{
if (!(IdToNode(id, mustExist) 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 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 InvalidProgramException($"An unexpected node {node.StrId()} was found during the 2nd schema model building pass.");
}
}
public static SchemaModel Create(Schema.CodeGeneratorRequest.Reader request)
{
var model = new SchemaModel(request);
model.Build();
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:X}";
public static string StrId(this ulong nodeId)
=> $"0x{nodeId: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

@ -1,9 +0,0 @@
namespace CapnpC.Model
{
enum SpecialName
{
NothingSpecial,
MethodParamsStruct,
MethodResultStruct
}
}

View File

@ -1,201 +0,0 @@
using Capnp;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
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<GenericParameter, Type> _parameterBindings =
new Dictionary<GenericParameter, Type>();
public Type(TypeTag tag)
{
Tag = tag;
}
public bool IsValueType
{
get
{
switch (Tag)
{
case TypeTag.AnyPointer:
case TypeTag.CapabilityPointer:
case TypeTag.Data:
case TypeTag.Group:
case TypeTag.Interface:
case TypeTag.List when ElementType.Tag != TypeTag.Void:
case TypeTag.ListPointer:
case TypeTag.Struct:
case TypeTag.StructPointer:
case TypeTag.Text:
case TypeTag.Void:
return false;
default:
return true;
}
}
}
public void InheritFreeParameters(IHasGenericParameters declaringType)
{
while (declaringType != null)
{
foreach (var p in declaringType.GetLocalTypeParameters())
{
if (!_parameterBindings.ContainsKey(p))
{
_parameterBindings[p] = Types.FromParameter(p);
}
}
declaringType = (declaringType as TypeDefinition)?.DeclaringElement as IHasGenericParameters;
}
}
Type SubstituteGenerics(Type type)
{
if (type == null)
{
return null;
}
if (type.Parameter != null)
{
if (_parameterBindings.TryGetValue(type.Parameter, out var boundType))
{
return boundType;
}
else
{
return Types.AnyPointer;
}
}
var stype = new Type(type.Tag)
{
Definition = type.Definition,
ElementType = SubstituteGenerics(type.ElementType)
};
foreach (var kvp in type._parameterBindings)
{
var p = kvp.Value.Parameter;
if (p != null && _parameterBindings.TryGetValue(p, out var boundType))
{
stype._parameterBindings[kvp.Key] = boundType;
}
else
{
stype._parameterBindings[kvp.Key] = kvp.Value;
}
}
return stype;
}
Field SubstituteGenerics(Field field)
{
var result = field.Clone();
result.Type = SubstituteGenerics(result.Type);
return result;
}
public new IReadOnlyList<Field> Fields => Definition.Fields.LazyListSelect(SubstituteGenerics);
public Type DeclaringType
{
get
{
var parentDef = Definition?.DeclaringElement as TypeDefinition;
// FIXME: Will become more sophisticated as soon as generics are implemented
return parentDef != null ? Types.FromDefinition(parentDef) : null;
}
}
public (int, Type) GetRank()
{
var cur = this;
int rank = 0;
while (cur.Tag == TypeTag.List)
{
cur = cur.ElementType;
++rank;
}
return (rank, cur);
}
public IEnumerable<Type> AllImplementedClasses
{
get
{
var stk = new Stack<Type>();
stk.Push(this);
var set = new HashSet<Type>();
while (stk.Count > 0)
{
var def = stk.Pop();
if (def == null)
{
break;
}
if (set.Add(def))
{
foreach (var super in def.Definition.Superclasses)
{
stk.Push(super);
}
}
}
return set;
}
}
public Type ResolveGenericParameter(GenericParameter genericParameter)
{
if (_parameterBindings.TryGetValue(genericParameter, out var type))
{
return type;
}
else
{
return Types.AnyPointer;
}
}
public void BindGenericParameter(GenericParameter genericParameter, Type boundType)
{
_parameterBindings.Add(genericParameter, boundType);
}
public override bool Equals(object obj)
{
return obj is Type other && Definition == other.Definition;
}
public override int GetHashCode()
{
return Definition?.GetHashCode() ?? 0;
}
}
}

View File

@ -1,8 +0,0 @@
namespace CapnpC.Model
{
enum TypeCategory
{
Value,
Pointer
}
}

View File

@ -1,88 +0,0 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace CapnpC.Model
{
class TypeDefinition : AbstractType, IDefinition, IHasNestedDefinitions, IHasGenericParameters
{
public class DiscriminationInfo
{
public DiscriminationInfo(ushort numOptions, uint tagOffset)
{
NumOptions = numOptions;
TagOffset = tagOffset;
}
public ushort NumOptions { get; }
public uint TagOffset { get; }
}
public TypeDefinition(TypeTag tag, ulong id, IHasNestedDefinitions parent)
{
Trace.Assert(parent != null);
Tag = tag;
Id = id;
IsGenerated = (parent as IDefinition).IsGenerated;
DeclaringElement = parent;
if (tag == TypeTag.Group)
((TypeDefinition)parent).NestedGroups.Add(this);
else
parent.NestedDefinitions.Add(this);
}
public ulong Id { get; }
public bool IsGenerated { get; }
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<Field> Fields => base.Fields;
public List<Enumerant> Enumerants { get; } = new List<Enumerant>();
public ICollection<IDefinition> NestedDefinitions { get; } = new List<IDefinition>();
public IEnumerable<TypeDefinition> NestedTypes { get => this.GetNestedTypes(); }
public List<TypeDefinition> NestedGroups { get; } = new List<TypeDefinition>();
public ICollection<Constant> Constants { get; } = new List<Constant>();
public List<Method> Methods { get; } = new List<Method>();
public List<Type> Superclasses { get; } = new List<Type>();
public List<string> GenericParameters { get; } = new List<string>();
public bool IsGeneric { get; set; }
public ushort StructDataWordCount { get; set; }
public ushort StructPointerCount { get; set; }
public IEnumerable<TypeDefinition> DefinitionHierarchy
{
get
{
IHasNestedDefinitions cur = this;
while (cur is TypeDefinition def)
{
yield return def;
cur = def.DeclaringElement;
}
}
}
public GenFile File
{
get
{
IHasNestedDefinitions cur = this;
while (cur is TypeDefinition def) cur = def.DeclaringElement;
return cur as GenFile;
}
}
public IEnumerable<GenericParameter> AllTypeParameters
{
get
{
return from def in DefinitionHierarchy
from p in def.GetLocalTypeParameters()
select p;
}
}
}
}

View File

@ -1,36 +0,0 @@
namespace CapnpC.Model
{
enum TypeTag
{
Unknown,
Void,
Bool,
S8,
U8,
S16,
U16,
S32,
U32,
S64,
U64,
F32,
F64,
List,
Data,
Text,
AnyPointer,
StructPointer,
ListPointer,
CapabilityPointer,
ParameterPointer,
ImplicitMethodParameterPointer,
Struct,
Group,
Interface,
Enum,
AnyEnum,
Const,
Annotation,
File
}
}

View File

@ -1,56 +0,0 @@
using System;
namespace CapnpC.Model
{
static class Types
{
public static readonly Type Void = new Type(TypeTag.Void);
public static readonly Type Bool = new Type(TypeTag.Bool);
public static readonly Type S8 = new Type(TypeTag.S8);
public static readonly Type U8 = new Type(TypeTag.U8);
public static readonly Type S16 = new Type(TypeTag.S16);
public static readonly Type U16 = new Type(TypeTag.U16);
public static readonly Type S32 = new Type(TypeTag.S32);
public static readonly Type U32 = new Type(TypeTag.U32);
public static readonly Type S64 = new Type(TypeTag.S64);
public static readonly Type U64 = new Type(TypeTag.U64);
public static readonly Type F32 = new Type(TypeTag.F32);
public static readonly Type F64 = new Type(TypeTag.F64);
public static readonly Type AnyPointer = new Type(TypeTag.AnyPointer);
public static readonly Type StructPointer = new Type(TypeTag.StructPointer);
public static readonly Type ListPointer = new Type(TypeTag.ListPointer);
public static readonly Type CapabilityPointer = new Type(TypeTag.CapabilityPointer);
public static readonly Type Data = new Type(TypeTag.Data);
public static readonly Type Text = new Type(TypeTag.Text);
public static readonly Type AnyEnum = new Type(TypeTag.AnyEnum);
public static Type List(Type elementType)
{
return new Type(TypeTag.List)
{
ElementType = elementType
};
}
public static Type FromDefinition(TypeDefinition def)
{
if (def.Tag == TypeTag.Unknown)
{
throw new InvalidOperationException("Oops, type definition is not yet valid, cannot create type");
}
return new Type(def.Tag)
{
Definition = def
};
}
public static Type FromParameter(GenericParameter genericParameter)
{
return new Type(TypeTag.AnyPointer)
{
Parameter = genericParameter
};
}
}
}

Some files were not shown because too many files have changed in this diff Show More