21 changed files with 38 additions and 2020 deletions
@ -1,35 +0,0 @@ |
|||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|
||||
|
|
||||
<!-- Ensure that code generator is actually built --> |
|
||||
<ItemGroup> |
|
||||
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\src\tools\MicroComGenerator\MicroComGenerator.csproj"> |
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly> |
|
||||
<ExcludeAssets>all</ExcludeAssets> |
|
||||
<SkipGetTargetFrameworkProperties>true</SkipGetTargetFrameworkProperties> |
|
||||
<SetTargetFramework>TargetFramework=net6.0</SetTargetFramework> |
|
||||
</ProjectReference> |
|
||||
</ItemGroup> |
|
||||
|
|
||||
<Target Name="GenerateAvaloniaNativeComInterop" |
|
||||
BeforeTargets="CoreCompile" |
|
||||
DependsOnTargets="ResolveReferences" |
|
||||
Inputs="@(AvnComIdl);$(MSBuildThisFileDirectory)../src/tools/MicroComGenerator/**/*.cs" |
|
||||
Outputs="%(AvnComIdl.OutputFile)"> |
|
||||
<Message Importance="high" Text="Generating file %(AvnComIdl.OutputFile) from @(AvnComIdl)" /> |
|
||||
<Exec Command="dotnet "$(MSBuildThisFileDirectory)../src/tools/MicroComGenerator/bin/$(Configuration)/net6.0/MicroComGenerator.dll" -i @(AvnComIdl) --cs %(AvnComIdl.OutputFile)" |
|
||||
LogStandardErrorAsError="true" /> |
|
||||
<ItemGroup> |
|
||||
<!-- Remove and re-add generated file, this is needed for the clean build --> |
|
||||
<Compile Remove="%(AvnComIdl.OutputFile)"/> |
|
||||
<Compile Include="%(AvnComIdl.OutputFile)"/> |
|
||||
</ItemGroup> |
|
||||
</Target> |
|
||||
<ItemGroup> |
|
||||
<UpToDateCheckInput Include="@(AvnComIdl)"/> |
|
||||
<UpToDateCheckInput Include="$(MSBuildThisFileDirectory)/../src/tools/MicroComGenerator/**/*.cs"/> |
|
||||
</ItemGroup> |
|
||||
<PropertyGroup> |
|
||||
<_AvaloniaPatchComInterop>true</_AvaloniaPatchComInterop> |
|
||||
</PropertyGroup> |
|
||||
<Import Project="$(MSBuildThisFileDirectory)/BuildTargets.targets" /> |
|
||||
</Project> |
|
||||
@ -1,14 +1,14 @@ |
|||||
using System.IO; |
using System.IO; |
||||
using MicroComGenerator; |
using MicroCom.CodeGenerator; |
||||
using Nuke.Common; |
using Nuke.Common; |
||||
|
|
||||
partial class Build : NukeBuild |
partial class Build : NukeBuild |
||||
{ |
{ |
||||
Target GenerateCppHeaders => _ => _.Executes(() => |
Target GenerateCppHeaders => _ => _.Executes(() => |
||||
{ |
{ |
||||
var text = File.ReadAllText(RootDirectory / "src" / "Avalonia.Native" / "avn.idl"); |
var file = MicroComCodeGenerator.Parse( |
||||
var ast = AstParser.Parse(text); |
File.ReadAllText(RootDirectory / "src" / "Avalonia.Native" / "avn.idl")); |
||||
File.WriteAllText(RootDirectory / "native" / "Avalonia.Native" / "inc" / "avalonia-native.h", |
File.WriteAllText(RootDirectory / "native" / "Avalonia.Native" / "inc" / "avalonia-native.h", |
||||
CppGen.GenerateCpp(ast)); |
file.GenerateCppHeader()); |
||||
}); |
}); |
||||
} |
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
namespace System.Runtime.CompilerServices |
||||
|
{ |
||||
|
#if !NET5_0_OR_GREATER
|
||||
|
internal class ModuleInitializerAttribute : Attribute |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
#endif
|
||||
|
} |
||||
|
|
||||
@ -1,241 +0,0 @@ |
|||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Linq; |
|
||||
|
|
||||
namespace MicroComGenerator.Ast |
|
||||
{ |
|
||||
public class AstAttributeNode |
|
||||
{ |
|
||||
public string Name { get; set; } |
|
||||
public string Value { get; set; } |
|
||||
|
|
||||
public AstAttributeNode(string name, string value) |
|
||||
{ |
|
||||
Name = name; |
|
||||
Value = value; |
|
||||
} |
|
||||
|
|
||||
public override string ToString() => $"{Name} = {Value}"; |
|
||||
public AstAttributeNode Clone() => new AstAttributeNode(Name, Value); |
|
||||
} |
|
||||
|
|
||||
public class AstAttributes : List<AstAttributeNode> |
|
||||
{ |
|
||||
public bool HasAttribute(string a) => this.Any(x => x.Name == a); |
|
||||
|
|
||||
public AstAttributes Clone() |
|
||||
{ |
|
||||
var rv= new AstAttributes(); |
|
||||
rv.AddRange(this.Select(x => x.Clone())); |
|
||||
return rv; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public interface IAstNodeWithAttributes |
|
||||
{ |
|
||||
public AstAttributes Attributes { get; set; } |
|
||||
} |
|
||||
|
|
||||
public class AstEnumNode : List<AstEnumMemberNode>, IAstNodeWithAttributes |
|
||||
{ |
|
||||
public AstAttributes Attributes { get; set; } = new AstAttributes(); |
|
||||
public string Name { get; set; } |
|
||||
public override string ToString() => "Enum " + Name; |
|
||||
|
|
||||
public AstEnumNode Clone() |
|
||||
{ |
|
||||
var rv = new AstEnumNode { Name = Name, Attributes = Attributes.Clone() }; |
|
||||
rv.AddRange(this.Select(x => x.Clone())); |
|
||||
return rv; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public class AstEnumMemberNode |
|
||||
{ |
|
||||
public string Name { get; set; } |
|
||||
public string Value { get; set; } |
|
||||
|
|
||||
public AstEnumMemberNode(string name, string value) |
|
||||
{ |
|
||||
Name = name; |
|
||||
Value = value; |
|
||||
} |
|
||||
|
|
||||
public override string ToString() => $"Enum member {Name} = {Value}"; |
|
||||
public AstEnumMemberNode Clone() => new AstEnumMemberNode(Name, Value); |
|
||||
} |
|
||||
|
|
||||
public class AstStructNode : List<AstStructMemberNode>, IAstNodeWithAttributes |
|
||||
{ |
|
||||
public AstAttributes Attributes { get; set; } = new AstAttributes(); |
|
||||
public string Name { get; set; } |
|
||||
public override string ToString() => "Struct " + Name; |
|
||||
|
|
||||
public AstStructNode Clone() |
|
||||
{ |
|
||||
var rv = new AstStructNode { Name = Name, Attributes = Attributes.Clone() }; |
|
||||
rv.AddRange(this.Select(x => x.Clone())); |
|
||||
return rv; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public class AstTypeNode |
|
||||
{ |
|
||||
public string Name { get; set; } |
|
||||
public int PointerLevel { get; set; } |
|
||||
public bool IsLink { get; set; } |
|
||||
|
|
||||
public string Format() => Name + new string('*', PointerLevel) |
|
||||
+ (IsLink ? "&" : ""); |
|
||||
public override string ToString() => Format(); |
|
||||
public AstTypeNode Clone() => new AstTypeNode() { |
|
||||
Name = Name, |
|
||||
PointerLevel = PointerLevel, |
|
||||
IsLink = IsLink |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
public class AstStructMemberNode : IAstNodeWithAttributes |
|
||||
{ |
|
||||
public string Name { get; set; } |
|
||||
public AstTypeNode Type { get; set; } |
|
||||
|
|
||||
public override string ToString() => $"Struct member {Type.Format()} {Name}"; |
|
||||
public AstStructMemberNode Clone() => new AstStructMemberNode() { Name = Name, Type = Type.Clone() }; |
|
||||
public AstAttributes Attributes { get; set; } = new AstAttributes(); |
|
||||
} |
|
||||
|
|
||||
public class AstInterfaceNode : List<AstInterfaceMemberNode>, IAstNodeWithAttributes |
|
||||
{ |
|
||||
public AstAttributes Attributes { get; set; } = new AstAttributes(); |
|
||||
public string Name { get; set; } |
|
||||
public string Inherits { get; set; } |
|
||||
|
|
||||
public override string ToString() |
|
||||
{ |
|
||||
if (Inherits == null) |
|
||||
return Name; |
|
||||
return $"Interface {Name} : {Inherits}"; |
|
||||
} |
|
||||
public AstInterfaceNode Clone() |
|
||||
{ |
|
||||
var rv = new AstInterfaceNode { Name = Name, Inherits = Inherits, Attributes = Attributes.Clone() }; |
|
||||
rv.AddRange(this.Select(x => x.Clone())); |
|
||||
return rv; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public class AstInterfaceMemberNode : List<AstInterfaceMemberArgumentNode>, IAstNodeWithAttributes |
|
||||
{ |
|
||||
public string Name { get; set; } |
|
||||
public AstTypeNode ReturnType { get; set; } |
|
||||
public AstAttributes Attributes { get; set; } = new AstAttributes(); |
|
||||
|
|
||||
public AstInterfaceMemberNode Clone() |
|
||||
{ |
|
||||
var rv = new AstInterfaceMemberNode() |
|
||||
{ |
|
||||
Name = Name, Attributes = Attributes.Clone(), ReturnType = ReturnType |
|
||||
}; |
|
||||
rv.AddRange(this.Select(x => x.Clone())); |
|
||||
return rv; |
|
||||
} |
|
||||
|
|
||||
public override string ToString() => |
|
||||
$"Interface member {ReturnType.Format()} {Name} ({string.Join(", ", this.Select(x => x.Format()))})"; |
|
||||
} |
|
||||
|
|
||||
public class AstInterfaceMemberArgumentNode : IAstNodeWithAttributes |
|
||||
{ |
|
||||
public string Name { get; set; } |
|
||||
public AstTypeNode Type { get; set; } |
|
||||
public AstAttributes Attributes { get; set; } = new AstAttributes(); |
|
||||
|
|
||||
|
|
||||
public string Format() => $"{Type.Format()} {Name}"; |
|
||||
public override string ToString() => "Argument " + Format(); |
|
||||
|
|
||||
public AstInterfaceMemberArgumentNode Clone() => new AstInterfaceMemberArgumentNode |
|
||||
{ |
|
||||
Name = Name, Type = Type.Clone(), Attributes = Attributes.Clone() |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
public static class AstExtensions |
|
||||
{ |
|
||||
public static bool HasAttribute(this IAstNodeWithAttributes node, string s) => node.Attributes.HasAttribute(s); |
|
||||
|
|
||||
public static string GetAttribute(this IAstNodeWithAttributes node, string s) |
|
||||
{ |
|
||||
var value = node.Attributes.FirstOrDefault(a => a.Name == s)?.Value; |
|
||||
if (value == null) |
|
||||
throw new CodeGenException("Expected attribute " + s + " for node " + node); |
|
||||
return value; |
|
||||
} |
|
||||
|
|
||||
public static string GetAttributeOrDefault(this IAstNodeWithAttributes node, string s) |
|
||||
=> node.Attributes.FirstOrDefault(a => a.Name == s)?.Value; |
|
||||
} |
|
||||
|
|
||||
class AstVisitor |
|
||||
{ |
|
||||
protected virtual void VisitType(AstTypeNode type) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
protected virtual void VisitArgument(AstInterfaceMemberArgumentNode argument) |
|
||||
{ |
|
||||
VisitType(argument.Type); |
|
||||
} |
|
||||
|
|
||||
protected virtual void VisitInterfaceMember(AstInterfaceMemberNode member) |
|
||||
{ |
|
||||
foreach(var a in member) |
|
||||
VisitArgument(a); |
|
||||
VisitType(member.ReturnType); |
|
||||
} |
|
||||
|
|
||||
protected virtual void VisitInterface(AstInterfaceNode iface) |
|
||||
{ |
|
||||
foreach(var m in iface) |
|
||||
VisitInterfaceMember(m); |
|
||||
} |
|
||||
|
|
||||
protected virtual void VisitStructMember(AstStructMemberNode member) |
|
||||
{ |
|
||||
VisitType(member.Type); |
|
||||
} |
|
||||
|
|
||||
protected virtual void VisitStruct(AstStructNode node) |
|
||||
{ |
|
||||
foreach(var m in node) |
|
||||
VisitStructMember(m); |
|
||||
} |
|
||||
|
|
||||
public virtual void VisitAst(AstIdlNode ast) |
|
||||
{ |
|
||||
foreach(var iface in ast.Interfaces) |
|
||||
VisitInterface(iface); |
|
||||
foreach (var s in ast.Structs) |
|
||||
VisitStruct(s); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
} |
|
||||
|
|
||||
public class AstIdlNode : IAstNodeWithAttributes |
|
||||
{ |
|
||||
public AstAttributes Attributes { get; set; } = new AstAttributes(); |
|
||||
public List<AstEnumNode> Enums { get; set; } = new List<AstEnumNode>(); |
|
||||
public List<AstStructNode> Structs { get; set; } = new List<AstStructNode>(); |
|
||||
public List<AstInterfaceNode> Interfaces { get; set; } = new List<AstInterfaceNode>(); |
|
||||
|
|
||||
public AstIdlNode Clone() => new AstIdlNode() |
|
||||
{ |
|
||||
Attributes = Attributes.Clone(), |
|
||||
Enums = Enums.Select(x => x.Clone()).ToList(), |
|
||||
Structs = Structs.Select(x => x.Clone()).ToList(), |
|
||||
Interfaces = Interfaces.Select(x => x.Clone()).ToList() |
|
||||
}; |
|
||||
} |
|
||||
} |
|
||||
@ -1,232 +0,0 @@ |
|||||
using System.Collections.Generic; |
|
||||
using MicroComGenerator.Ast; |
|
||||
|
|
||||
namespace MicroComGenerator |
|
||||
{ |
|
||||
public class AstParser |
|
||||
{ |
|
||||
public static AstIdlNode Parse(string source) |
|
||||
{ |
|
||||
var parser = new TokenParser(source); |
|
||||
var idl = new AstIdlNode { Attributes = ParseGlobalAttributes(ref parser) }; |
|
||||
|
|
||||
while (!parser.Eof) |
|
||||
{ |
|
||||
var attrs = ParseLocalAttributes(ref parser); |
|
||||
if (parser.TryConsume(";")) |
|
||||
continue; |
|
||||
if (parser.TryParseKeyword("enum")) |
|
||||
idl.Enums.Add(ParseEnum(attrs, ref parser)); |
|
||||
else if (parser.TryParseKeyword("struct")) |
|
||||
idl.Structs.Add(ParseStruct(attrs, ref parser)); |
|
||||
else if (parser.TryParseKeyword("interface")) |
|
||||
idl.Interfaces.Add(ParseInterface(attrs, ref parser)); |
|
||||
else |
|
||||
throw new ParseException("Unexpected character", ref parser); |
|
||||
} |
|
||||
|
|
||||
return idl; |
|
||||
} |
|
||||
|
|
||||
static AstAttributes ParseGlobalAttributes(ref TokenParser parser) |
|
||||
{ |
|
||||
var rv = new AstAttributes(); |
|
||||
while (!parser.Eof) |
|
||||
{ |
|
||||
parser.SkipWhitespace(); |
|
||||
if (parser.TryConsume('@')) |
|
||||
{ |
|
||||
var ident = parser.ParseIdentifier("-"); |
|
||||
var value = parser.ReadToEol().Trim(); |
|
||||
if (value == "@@") |
|
||||
{ |
|
||||
parser.Advance(1); |
|
||||
value = ""; |
|
||||
while (true) |
|
||||
{ |
|
||||
var l = parser.ReadToEol(); |
|
||||
if (l == "@@") |
|
||||
break; |
|
||||
else |
|
||||
value = value.Length == 0 ? l : (value + "\n" + l); |
|
||||
parser.Advance(1); |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
rv.Add(new AstAttributeNode(ident, value)); |
|
||||
} |
|
||||
else |
|
||||
return rv; |
|
||||
} |
|
||||
|
|
||||
return rv; |
|
||||
} |
|
||||
|
|
||||
static AstAttributes ParseLocalAttributes(ref TokenParser parser) |
|
||||
{ |
|
||||
var rv = new AstAttributes(); |
|
||||
while (parser.TryConsume("[")) |
|
||||
{ |
|
||||
while (!parser.TryConsume("]") && !parser.Eof) |
|
||||
{ |
|
||||
if (parser.TryConsume(',')) |
|
||||
continue; |
|
||||
|
|
||||
// Get identifier
|
|
||||
var ident = parser.ParseIdentifier("-"); |
|
||||
|
|
||||
// No value, end of attribute list
|
|
||||
if (parser.TryConsume(']')) |
|
||||
{ |
|
||||
rv.Add(new AstAttributeNode(ident, null)); |
|
||||
break; |
|
||||
} |
|
||||
// No value, next attribute
|
|
||||
else if (parser.TryConsume(',')) |
|
||||
rv.Add(new AstAttributeNode(ident, null)); |
|
||||
// Has value
|
|
||||
else if (parser.TryConsume('(')) |
|
||||
{ |
|
||||
var value = parser.ReadTo(')'); |
|
||||
parser.Consume(')'); |
|
||||
rv.Add(new AstAttributeNode(ident, value)); |
|
||||
} |
|
||||
else |
|
||||
throw new ParseException("Unexpected character", ref parser); |
|
||||
} |
|
||||
|
|
||||
if (parser.Eof) |
|
||||
throw new ParseException("Unexpected EOF", ref parser); |
|
||||
} |
|
||||
|
|
||||
return rv; |
|
||||
} |
|
||||
|
|
||||
static void EnsureOpenBracket(ref TokenParser parser) |
|
||||
{ |
|
||||
if (!parser.TryConsume('{')) |
|
||||
throw new ParseException("{ expected", ref parser); |
|
||||
} |
|
||||
|
|
||||
static AstEnumNode ParseEnum(AstAttributes attrs, ref TokenParser parser) |
|
||||
{ |
|
||||
var name = parser.ParseIdentifier(); |
|
||||
EnsureOpenBracket(ref parser); |
|
||||
var rv = new AstEnumNode { Name = name, Attributes = attrs }; |
|
||||
while (!parser.TryConsume('}') && !parser.Eof) |
|
||||
{ |
|
||||
if (parser.TryConsume(',')) |
|
||||
continue; |
|
||||
|
|
||||
var ident = parser.ParseIdentifier(); |
|
||||
|
|
||||
// Automatic value
|
|
||||
if (parser.TryConsume(',') || parser.Peek == '}') |
|
||||
{ |
|
||||
rv.Add(new AstEnumMemberNode(ident, null)); |
|
||||
continue; |
|
||||
} |
|
||||
|
|
||||
if (!parser.TryConsume('=')) |
|
||||
throw new ParseException("Unexpected character", ref parser); |
|
||||
|
|
||||
var value = parser.ReadToAny(",}").Trim(); |
|
||||
rv.Add(new AstEnumMemberNode(ident, value)); |
|
||||
|
|
||||
if (parser.Eof) |
|
||||
throw new ParseException("Unexpected EOF", ref parser); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
return rv; |
|
||||
} |
|
||||
|
|
||||
static AstTypeNode ParseType(ref TokenParser parser) |
|
||||
{ |
|
||||
var ident = parser.ParseIdentifier(); |
|
||||
var t = new AstTypeNode { Name = ident }; |
|
||||
while (parser.TryConsume('*')) |
|
||||
t.PointerLevel++; |
|
||||
if (parser.TryConsume("&")) |
|
||||
t.IsLink = true; |
|
||||
return t; |
|
||||
} |
|
||||
|
|
||||
static AstStructNode ParseStruct(AstAttributes attrs, ref TokenParser parser) |
|
||||
{ |
|
||||
var name = parser.ParseIdentifier(); |
|
||||
EnsureOpenBracket(ref parser); |
|
||||
var rv = new AstStructNode { Name = name, Attributes = attrs }; |
|
||||
while (!parser.TryConsume('}') && !parser.Eof) |
|
||||
{ |
|
||||
var memberAttrs = ParseLocalAttributes(ref parser); |
|
||||
var t = ParseType(ref parser); |
|
||||
bool parsedAtLeastOneMember = false; |
|
||||
while (!parser.TryConsume(';')) |
|
||||
{ |
|
||||
// Skip any ,
|
|
||||
while (parser.TryConsume(',')) { } |
|
||||
|
|
||||
var ident = parser.ParseIdentifier(); |
|
||||
parsedAtLeastOneMember = true; |
|
||||
rv.Add(new AstStructMemberNode { Name = ident, Type = t, Attributes = memberAttrs}); |
|
||||
} |
|
||||
|
|
||||
if (!parsedAtLeastOneMember) |
|
||||
throw new ParseException("Expected at least one enum member with declared type " + t, ref parser); |
|
||||
} |
|
||||
|
|
||||
return rv; |
|
||||
} |
|
||||
|
|
||||
static AstInterfaceNode ParseInterface(AstAttributes interfaceAttrs, ref TokenParser parser) |
|
||||
{ |
|
||||
var interfaceName = parser.ParseIdentifier(); |
|
||||
string inheritsFrom = null; |
|
||||
if (parser.TryConsume(":")) |
|
||||
inheritsFrom = parser.ParseIdentifier(); |
|
||||
|
|
||||
EnsureOpenBracket(ref parser); |
|
||||
var rv = new AstInterfaceNode |
|
||||
{ |
|
||||
Name = interfaceName, Attributes = interfaceAttrs, Inherits = inheritsFrom |
|
||||
}; |
|
||||
while (!parser.TryConsume('}') && !parser.Eof) |
|
||||
{ |
|
||||
var memberAttrs = ParseLocalAttributes(ref parser); |
|
||||
var returnType = ParseType(ref parser); |
|
||||
var name = parser.ParseIdentifier(); |
|
||||
var member = new AstInterfaceMemberNode |
|
||||
{ |
|
||||
Name = name, ReturnType = returnType, Attributes = memberAttrs |
|
||||
}; |
|
||||
rv.Add(member); |
|
||||
|
|
||||
parser.Consume('('); |
|
||||
while (true) |
|
||||
{ |
|
||||
if (parser.TryConsume(')')) |
|
||||
break; |
|
||||
|
|
||||
var argumentAttrs = ParseLocalAttributes(ref parser); |
|
||||
var type = ParseType(ref parser); |
|
||||
var argName = parser.ParseIdentifier(); |
|
||||
member.Add(new AstInterfaceMemberArgumentNode |
|
||||
{ |
|
||||
Name = argName, Type = type, Attributes = argumentAttrs |
|
||||
}); |
|
||||
|
|
||||
if (parser.TryConsume(')')) |
|
||||
break; |
|
||||
if (parser.TryConsume(',')) |
|
||||
continue; |
|
||||
throw new ParseException("Unexpected character", ref parser); |
|
||||
} |
|
||||
|
|
||||
parser.Consume(';'); |
|
||||
} |
|
||||
|
|
||||
return rv; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,484 +0,0 @@ |
|||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Linq; |
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using MicroComGenerator.Ast; |
|
||||
using Microsoft.CodeAnalysis; |
|
||||
using Microsoft.CodeAnalysis.CSharp; |
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax; |
|
||||
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; |
|
||||
|
|
||||
// ReSharper disable CoVariantArrayConversion
|
|
||||
|
|
||||
// HERE BE DRAGONS
|
|
||||
|
|
||||
namespace MicroComGenerator |
|
||||
{ |
|
||||
public partial class CSharpGen |
|
||||
{ |
|
||||
abstract class Arg |
|
||||
{ |
|
||||
public string Name; |
|
||||
public string NativeType; |
|
||||
public AstAttributes Attributes { get; set; } |
|
||||
public virtual StatementSyntax CreateFixed(StatementSyntax inner) => inner; |
|
||||
|
|
||||
public virtual void PreMarshal(List<StatementSyntax> body) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public virtual void PreMarshalForReturn(List<StatementSyntax> body) => |
|
||||
throw new InvalidOperationException("Don't know how to use " + NativeType + " as HRESULT-return"); |
|
||||
|
|
||||
public virtual ExpressionSyntax Value(bool isHresultReturn) => ParseExpression(Name); |
|
||||
public abstract string ManagedType { get; } |
|
||||
public virtual string ReturnManagedType => ManagedType; |
|
||||
|
|
||||
public virtual StatementSyntax[] ReturnMarshalResult() => new[] { ParseStatement("return " + Name + ";") }; |
|
||||
|
|
||||
|
|
||||
public virtual void BackPreMarshal(List<StatementSyntax> body) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public virtual ExpressionSyntax BackMarshalValue() => ParseExpression(Name); |
|
||||
public virtual ExpressionSyntax BackMarshalReturn(string resultVar) => ParseExpression(resultVar); |
|
||||
|
|
||||
} |
|
||||
|
|
||||
class InterfaceReturnArg : Arg |
|
||||
{ |
|
||||
public string InterfaceType; |
|
||||
public override ExpressionSyntax Value(bool isHresultReturn) => ParseExpression("&" + PName); |
|
||||
public override string ManagedType => InterfaceType; |
|
||||
|
|
||||
private string PName => "__marshal_" + Name; |
|
||||
|
|
||||
public override void PreMarshalForReturn(List<StatementSyntax> body) |
|
||||
{ |
|
||||
body.Add(ParseStatement("void* " + PName + " = null;")); |
|
||||
} |
|
||||
|
|
||||
public override StatementSyntax[] ReturnMarshalResult() => new[] |
|
||||
{ |
|
||||
ParseStatement("return Avalonia.MicroCom.MicroComRuntime.CreateProxyFor<" + InterfaceType + ">(" + |
|
||||
PName + ", true);") |
|
||||
}; |
|
||||
|
|
||||
public override ExpressionSyntax BackMarshalValue() |
|
||||
{ |
|
||||
return ParseExpression("INVALID"); |
|
||||
} |
|
||||
|
|
||||
public override ExpressionSyntax BackMarshalReturn(string resultVar) |
|
||||
{ |
|
||||
return ParseExpression($"Avalonia.MicroCom.MicroComRuntime.GetNativePointer({resultVar}, true)"); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
class InterfaceArg : Arg |
|
||||
{ |
|
||||
public string InterfaceType; |
|
||||
|
|
||||
public override ExpressionSyntax Value(bool isHresultReturn) => |
|
||||
ParseExpression("Avalonia.MicroCom.MicroComRuntime.GetNativePointer(" + Name + ")"); |
|
||||
|
|
||||
public override string ManagedType => InterfaceType; |
|
||||
|
|
||||
public override StatementSyntax[] ReturnMarshalResult() => new[] |
|
||||
{ |
|
||||
ParseStatement("return Avalonia.MicroCom.MicroComRuntime.CreateProxyFor<" + InterfaceType + ">(" + |
|
||||
Name + ", true);") |
|
||||
}; |
|
||||
|
|
||||
public override ExpressionSyntax BackMarshalValue() |
|
||||
{ |
|
||||
return ParseExpression("Avalonia.MicroCom.MicroComRuntime.CreateProxyFor<" + InterfaceType + ">(" + |
|
||||
Name + ", false)"); |
|
||||
} |
|
||||
|
|
||||
public override ExpressionSyntax BackMarshalReturn(string resultVar) |
|
||||
{ |
|
||||
return ParseExpression($"Avalonia.MicroCom.MicroComRuntime.GetNativePointer({resultVar}, true)"); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
class BypassArg : Arg |
|
||||
{ |
|
||||
public string Type { get; set; } |
|
||||
public int PointerLevel; |
|
||||
public override string ManagedType => Type + new string('*', PointerLevel); |
|
||||
public override string ReturnManagedType => Type + new string('*', PointerLevel - 1); |
|
||||
|
|
||||
public override ExpressionSyntax Value(bool isHresultReturn) |
|
||||
{ |
|
||||
if (isHresultReturn) |
|
||||
return ParseExpression("&" + Name); |
|
||||
return base.Value(false); |
|
||||
} |
|
||||
|
|
||||
public override void PreMarshalForReturn(List<StatementSyntax> body) |
|
||||
{ |
|
||||
if (PointerLevel == 0) |
|
||||
base.PreMarshalForReturn(body); |
|
||||
else |
|
||||
body.Add(ParseStatement(Type + new string('*', PointerLevel - 1) + " " + Name + "=default;")); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
class StringArg : Arg |
|
||||
{ |
|
||||
private string BName => "__bytemarshal_" + Name; |
|
||||
private string FName => "__fixedmarshal_" + Name; |
|
||||
|
|
||||
public override void PreMarshal(List<StatementSyntax> body) |
|
||||
{ |
|
||||
body.Add(ParseStatement($"var {BName} = new byte[System.Text.Encoding.UTF8.GetByteCount({Name})+1];")); |
|
||||
body.Add(ParseStatement($"System.Text.Encoding.UTF8.GetBytes({Name}, 0, {Name}.Length, {BName}, 0);")); |
|
||||
} |
|
||||
|
|
||||
public override StatementSyntax CreateFixed(StatementSyntax inner) |
|
||||
{ |
|
||||
return FixedStatement(DeclareVar("byte*", FName, ParseExpression(BName)), inner); |
|
||||
} |
|
||||
|
|
||||
public override ExpressionSyntax Value(bool isHresultReturn) => ParseExpression(FName); |
|
||||
public override string ManagedType => "string"; |
|
||||
public override ExpressionSyntax BackMarshalValue() |
|
||||
{ |
|
||||
return ParseExpression( |
|
||||
$"({Name} == null ? null : System.Runtime.InteropServices.Marshal.PtrToStringAnsi(new IntPtr(" + Name + ")))"); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
string ConvertNativeType(string type) |
|
||||
{ |
|
||||
if (type == "size_t") |
|
||||
return "System.IntPtr"; |
|
||||
if (type == "HRESULT") |
|
||||
return "int"; |
|
||||
return type; |
|
||||
} |
|
||||
|
|
||||
Arg ConvertArg(AstInterfaceMemberArgumentNode node) |
|
||||
{ |
|
||||
var arg = ConvertArg(node.Name, node.Type); |
|
||||
arg.Attributes = node.Attributes.Clone(); |
|
||||
return arg; |
|
||||
} |
|
||||
|
|
||||
Arg ConvertArg(string name, AstTypeNode type) |
|
||||
{ |
|
||||
type = new AstTypeNode { Name = ConvertNativeType(type.Name), PointerLevel = type.PointerLevel }; |
|
||||
|
|
||||
if (type.PointerLevel == 2) |
|
||||
{ |
|
||||
if (IsInterface(type)) |
|
||||
return new InterfaceReturnArg { Name = name, InterfaceType = type.Name, NativeType = "void**" }; |
|
||||
} |
|
||||
else if (type.PointerLevel == 1) |
|
||||
{ |
|
||||
if (IsInterface(type)) |
|
||||
return new InterfaceArg { Name = name, InterfaceType = type.Name, NativeType = "void*" }; |
|
||||
if (type.Name == "char") |
|
||||
return new StringArg { Name = name, NativeType = "byte*" }; |
|
||||
} |
|
||||
|
|
||||
return new BypassArg |
|
||||
{ |
|
||||
Name = name, Type = type.Name, PointerLevel = type.PointerLevel, NativeType = type.ToString() |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
|
|
||||
void GenerateInterfaceMember(AstInterfaceMemberNode member, ref InterfaceDeclarationSyntax iface, |
|
||||
ref ClassDeclarationSyntax proxy, ref ClassDeclarationSyntax vtbl, |
|
||||
List<StatementSyntax> vtblCtor, int num) |
|
||||
{ |
|
||||
// Prepare method information
|
|
||||
if (member.Name == "GetRenderingDevice") |
|
||||
Console.WriteLine(); |
|
||||
var args = member.Select(ConvertArg).ToList(); |
|
||||
var returnArg = ConvertArg("__result", member.ReturnType); |
|
||||
bool isHresult = member.ReturnType.Name == "HRESULT"; |
|
||||
bool isHresultLastArgumentReturn = isHresult |
|
||||
&& args.Count > 0 |
|
||||
&& (args.Last().Name == "ppv" |
|
||||
|| args.Last().Name == "retOut" |
|
||||
|| args.Last().Name == "ret" |
|
||||
|| args.Last().Attributes.HasAttribute("out") |
|
||||
|| args.Last().Attributes.HasAttribute("retval") |
|
||||
) |
|
||||
&& ((member.Last().Type.PointerLevel > 0 |
|
||||
&& !IsInterface(member.Last().Type)) |
|
||||
|| member.Last().Type.PointerLevel == 2); |
|
||||
|
|
||||
bool isVoidReturn = member.ReturnType.Name == "void" && member.ReturnType.PointerLevel == 0; |
|
||||
|
|
||||
|
|
||||
// Generate method signature
|
|
||||
MethodDeclarationSyntax GenerateManagedSig(string returnType, string name, |
|
||||
IEnumerable<(string n, string t)> args) |
|
||||
=> MethodDeclaration(ParseTypeName(returnType), name).WithParameterList( |
|
||||
ParameterList( |
|
||||
SeparatedList(args.Select(x => Parameter(Identifier(x.n)).WithType(ParseTypeName(x.t)))))); |
|
||||
|
|
||||
var managedSig = |
|
||||
isHresult ? |
|
||||
GenerateManagedSig(isHresultLastArgumentReturn ? args.Last().ReturnManagedType : "void", |
|
||||
member.Name, |
|
||||
(isHresultLastArgumentReturn ? args.SkipLast(1) : args).Select(a => (a.Name, a.ManagedType))) : |
|
||||
GenerateManagedSig(returnArg.ManagedType, member.Name, args.Select(a => (a.Name, a.ManagedType))); |
|
||||
|
|
||||
iface = iface.AddMembers(managedSig.WithSemicolonToken(Semicolon())); |
|
||||
|
|
||||
// Prepare args for marshaling
|
|
||||
var preMarshal = new List<StatementSyntax>(); |
|
||||
if (!isVoidReturn) |
|
||||
preMarshal.Add(ParseStatement(returnArg.NativeType + " __result;")); |
|
||||
|
|
||||
for (var idx = 0; idx < args.Count; idx++) |
|
||||
{ |
|
||||
if (isHresultLastArgumentReturn && idx == args.Count - 1) |
|
||||
args[idx].PreMarshalForReturn(preMarshal); |
|
||||
else |
|
||||
args[idx].PreMarshal(preMarshal); |
|
||||
} |
|
||||
|
|
||||
// Generate call expression
|
|
||||
ExpressionSyntax callExpr = InvocationExpression(_localInterop.GetCaller(returnArg.NativeType, |
|
||||
args.Select(x => x.NativeType).ToList())) |
|
||||
.AddArgumentListArguments(Argument(ParseExpression("PPV"))) |
|
||||
.AddArgumentListArguments(args |
|
||||
.Select((a, i) => Argument(a.Value(isHresultLastArgumentReturn && i == args.Count - 1))).ToArray()) |
|
||||
.AddArgumentListArguments(Argument(ParseExpression("(*PPV)[base.VTableSize + " + num + "]"))); |
|
||||
|
|
||||
if (!isVoidReturn) |
|
||||
callExpr = CastExpression(ParseTypeName(returnArg.NativeType), callExpr); |
|
||||
|
|
||||
// Save call result if needed
|
|
||||
if (!isVoidReturn) |
|
||||
callExpr = AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, ParseExpression("__result"), |
|
||||
callExpr); |
|
||||
|
|
||||
|
|
||||
// Wrap call into fixed() blocks
|
|
||||
StatementSyntax callStatement = ExpressionStatement(callExpr); |
|
||||
foreach (var arg in args) |
|
||||
callStatement = arg.CreateFixed(callStatement); |
|
||||
|
|
||||
// Build proxy body
|
|
||||
var proxyBody = Block() |
|
||||
.AddStatements(preMarshal.ToArray()) |
|
||||
.AddStatements(callStatement); |
|
||||
|
|
||||
// Process return value
|
|
||||
if (!isVoidReturn) |
|
||||
{ |
|
||||
if (isHresult) |
|
||||
{ |
|
||||
proxyBody = proxyBody.AddStatements( |
|
||||
ParseStatement( |
|
||||
$"if(__result != 0) throw new System.Runtime.InteropServices.COMException(\"{member.Name} failed\", __result);")); |
|
||||
|
|
||||
if (isHresultLastArgumentReturn) |
|
||||
proxyBody = proxyBody.AddStatements(args.Last().ReturnMarshalResult()); |
|
||||
} |
|
||||
else |
|
||||
proxyBody = proxyBody.AddStatements(returnArg.ReturnMarshalResult()); |
|
||||
} |
|
||||
|
|
||||
// Add the proxy method
|
|
||||
proxy = proxy.AddMembers(managedSig.AddModifiers(SyntaxKind.PublicKeyword) |
|
||||
.WithBody(proxyBody)); |
|
||||
|
|
||||
|
|
||||
// Generate VTable method
|
|
||||
var shadowDelegate = DelegateDeclaration(ParseTypeName(returnArg.NativeType), member.Name + "Delegate") |
|
||||
.AddParameterListParameters(Parameter(Identifier("@this")).WithType(ParseTypeName("IntPtr"))) |
|
||||
.AddParameterListParameters(args.Select(x => |
|
||||
Parameter(Identifier(x.Name)).WithType(ParseTypeName(x.NativeType))).ToArray()) |
|
||||
.AddAttribute("System.Runtime.InteropServices.UnmanagedFunctionPointer", |
|
||||
"System.Runtime.InteropServices.CallingConvention.StdCall"); |
|
||||
|
|
||||
var shadowMethod = MethodDeclaration(shadowDelegate.ReturnType, member.Name) |
|
||||
.WithParameterList(shadowDelegate.ParameterList) |
|
||||
.AddModifiers(Token(SyntaxKind.StaticKeyword)); |
|
||||
|
|
||||
var backPreMarshal = new List<StatementSyntax>(); |
|
||||
foreach (var arg in args) |
|
||||
arg.BackPreMarshal(backPreMarshal); |
|
||||
|
|
||||
backPreMarshal.Add( |
|
||||
ParseStatement($"__target = ({iface.Identifier.Text})Avalonia.MicroCom.MicroComRuntime.GetObjectFromCcw(@this);")); |
|
||||
|
|
||||
var isBackVoidReturn = isVoidReturn || (isHresult && !isHresultLastArgumentReturn); |
|
||||
|
|
||||
StatementSyntax backCallStatement; |
|
||||
|
|
||||
var backCallExpr = |
|
||||
IsPropertyRewriteCandidate(managedSig) ? |
|
||||
ParseExpression("__target." + member.Name.Substring(3)) : |
|
||||
InvocationExpression(ParseExpression("__target." + member.Name)) |
|
||||
.WithArgumentList(ArgumentList(SeparatedList( |
|
||||
(isHresultLastArgumentReturn ? args.SkipLast(1) : args) |
|
||||
.Select(a => |
|
||||
Argument(a.BackMarshalValue()))))); |
|
||||
|
|
||||
if (isBackVoidReturn) |
|
||||
backCallStatement = ExpressionStatement(backCallExpr); |
|
||||
else |
|
||||
{ |
|
||||
backCallStatement = LocalDeclarationStatement(DeclareVar("var", "__result", backCallExpr)); |
|
||||
if (isHresultLastArgumentReturn) |
|
||||
{ |
|
||||
backCallStatement = Block(backCallStatement, |
|
||||
ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, |
|
||||
ParseExpression("*" + args.Last().Name), |
|
||||
args.Last().BackMarshalReturn("__result") |
|
||||
))); |
|
||||
|
|
||||
} |
|
||||
else |
|
||||
backCallStatement = Block(backCallStatement, |
|
||||
ReturnStatement(returnArg.BackMarshalReturn("__result"))); |
|
||||
} |
|
||||
|
|
||||
BlockSyntax backBodyBlock = Block().AddStatements(backPreMarshal.ToArray()).AddStatements(backCallStatement); |
|
||||
|
|
||||
|
|
||||
var exceptions = new List<CatchClauseSyntax>() |
|
||||
{ |
|
||||
CatchClause( |
|
||||
CatchDeclaration(ParseTypeName("System.Exception"), Identifier("__exception__")), null, |
|
||||
Block( |
|
||||
ParseStatement( |
|
||||
"Avalonia.MicroCom.MicroComRuntime.UnhandledException(__target, __exception__);"), |
|
||||
isHresult ? ParseStatement("return unchecked((int)0x80004005u);") |
|
||||
: isVoidReturn ? EmptyStatement() : ParseStatement("return default;") |
|
||||
)) |
|
||||
}; |
|
||||
|
|
||||
if (isHresult) |
|
||||
exceptions.Insert(0, CatchClause( |
|
||||
CatchDeclaration(ParseTypeName("System.Runtime.InteropServices.COMException"), |
|
||||
Identifier("__com_exception__")), |
|
||||
null, Block(ParseStatement("return __com_exception__.ErrorCode;")))); |
|
||||
|
|
||||
backBodyBlock = Block( |
|
||||
TryStatement( |
|
||||
List(exceptions)) |
|
||||
.WithBlock(Block(backBodyBlock)) |
|
||||
); |
|
||||
if (isHresult) |
|
||||
backBodyBlock = backBodyBlock.AddStatements(ParseStatement("return 0;")); |
|
||||
|
|
||||
|
|
||||
backBodyBlock = Block() |
|
||||
.AddStatements(ParseStatement($"{iface.Identifier.Text} __target = null;")) |
|
||||
.AddStatements(backBodyBlock.Statements.ToArray()); |
|
||||
|
|
||||
shadowMethod = shadowMethod.WithBody(backBodyBlock); |
|
||||
|
|
||||
vtbl = vtbl.AddMembers(shadowDelegate).AddMembers(shadowMethod); |
|
||||
vtblCtor.Add(ParseStatement("base.AddMethod((" + shadowDelegate.Identifier.Text + ")" + |
|
||||
shadowMethod.Identifier.Text + ");")); |
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
} |
|
||||
|
|
||||
class LocalInteropHelper |
|
||||
{ |
|
||||
public ClassDeclarationSyntax Class { get; private set; } = ClassDeclaration("LocalInterop"); |
|
||||
private HashSet<string> _existing = new HashSet<string>(); |
|
||||
|
|
||||
public ExpressionSyntax GetCaller(string returnType, List<string> args) |
|
||||
{ |
|
||||
string ConvertType(string t) => t.EndsWith("*") ? "void*" : t; |
|
||||
returnType = ConvertType(returnType); |
|
||||
args = args.Select(ConvertType).ToList(); |
|
||||
|
|
||||
var name = "CalliStdCall" + returnType.Replace("*", "_ptr"); |
|
||||
var signature = returnType + "::" + name + "::" + string.Join("::", args); |
|
||||
if (_existing.Add(signature)) |
|
||||
{ |
|
||||
Class = Class.AddMembers(MethodDeclaration(ParseTypeName(returnType), name) |
|
||||
.AddModifiers(SyntaxKind.StaticKeyword, SyntaxKind.UnsafeKeyword, SyntaxKind.PublicKeyword) |
|
||||
.AddParameterListParameters(Parameter(Identifier("thisObj")).WithType(ParseTypeName("void*"))) |
|
||||
.AddParameterListParameters(args.Select((x, i) => |
|
||||
Parameter(Identifier("arg" + i)).WithType(ParseTypeName(x))).ToArray()) |
|
||||
.AddParameterListParameters(Parameter(Identifier("methodPtr")).WithType(ParseTypeName("void*"))) |
|
||||
.WithBody(Block(ExpressionStatement(ThrowExpression(ParseExpression("null")))))); |
|
||||
} |
|
||||
|
|
||||
return ParseExpression("LocalInterop." + name); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
|
|
||||
void GenerateInterface(ref NamespaceDeclarationSyntax ns, ref NamespaceDeclarationSyntax implNs, |
|
||||
AstInterfaceNode iface) |
|
||||
{ |
|
||||
var guidString = iface.GetAttribute("uuid"); |
|
||||
var inheritsUnknown = iface.Inherits == null || iface.Inherits == "IUnknown"; |
|
||||
|
|
||||
var ifaceDec = InterfaceDeclaration(iface.Name) |
|
||||
.WithBaseType(inheritsUnknown ? "Avalonia.MicroCom.IUnknown" : iface.Inherits) |
|
||||
.AddModifiers(Token(_visibility), Token(SyntaxKind.UnsafeKeyword), Token(SyntaxKind.PartialKeyword)); |
|
||||
|
|
||||
var proxyClassName = "__MicroCom" + iface.Name + "Proxy"; |
|
||||
var proxy = ClassDeclaration(proxyClassName) |
|
||||
.AddModifiers(Token(SyntaxKind.UnsafeKeyword), Token(_visibility), Token(SyntaxKind.PartialKeyword)) |
|
||||
.WithBaseType(inheritsUnknown ? |
|
||||
"Avalonia.MicroCom.MicroComProxyBase" : |
|
||||
("__MicroCom" + iface.Inherits + "Proxy")) |
|
||||
.AddBaseListTypes(SimpleBaseType(ParseTypeName(iface.Name))); |
|
||||
|
|
||||
|
|
||||
// Generate vtable
|
|
||||
var vtbl = ClassDeclaration("__MicroCom" + iface.Name + "VTable") |
|
||||
.AddModifiers(Token(SyntaxKind.UnsafeKeyword)); |
|
||||
|
|
||||
vtbl = vtbl.WithBaseType(inheritsUnknown ? |
|
||||
"Avalonia.MicroCom.MicroComVtblBase" : |
|
||||
"__MicroCom" + iface.Inherits + "VTable"); |
|
||||
|
|
||||
var vtblCtor = new List<StatementSyntax>(); |
|
||||
for (var idx = 0; idx < iface.Count; idx++) |
|
||||
GenerateInterfaceMember(iface[idx], ref ifaceDec, ref proxy, ref vtbl, vtblCtor, idx); |
|
||||
|
|
||||
vtbl = vtbl.AddMembers( |
|
||||
ConstructorDeclaration(vtbl.Identifier.Text) |
|
||||
.AddModifiers(Token(SyntaxKind.PublicKeyword)) |
|
||||
.WithBody(Block(vtblCtor)) |
|
||||
) |
|
||||
.AddMembers(MethodDeclaration(ParseTypeName("void"), "__MicroComModuleInit") |
|
||||
.AddModifiers(Token(SyntaxKind.StaticKeyword), Token(SyntaxKind.InternalKeyword)) |
|
||||
.WithExpressionBody(ArrowExpressionClause( |
|
||||
ParseExpression("Avalonia.MicroCom.MicroComRuntime.RegisterVTable(typeof(" + |
|
||||
iface.Name + "), new " + vtbl.Identifier.Text + "().CreateVTable())"))) |
|
||||
.WithSemicolonToken(Semicolon())); |
|
||||
|
|
||||
|
|
||||
// Finalize proxy code
|
|
||||
proxy = proxy.AddMembers( |
|
||||
MethodDeclaration(ParseTypeName("void"), "__MicroComModuleInit") |
|
||||
.AddModifiers(Token(SyntaxKind.StaticKeyword), Token(SyntaxKind.InternalKeyword)) |
|
||||
.WithBody(Block( |
|
||||
ParseStatement("Avalonia.MicroCom.MicroComRuntime.Register(typeof(" + |
|
||||
iface.Name + "), new Guid(\"" + guidString + "\"), (p, owns) => new " + |
|
||||
proxyClassName + "(p, owns));") |
|
||||
))) |
|
||||
.AddMembers(ParseMemberDeclaration("public " + proxyClassName + |
|
||||
"(IntPtr nativePointer, bool ownsHandle) : base(nativePointer, ownsHandle) {}")) |
|
||||
.AddMembers(ParseMemberDeclaration("protected override int VTableSize => base.VTableSize + " + |
|
||||
iface.Count + ";")); |
|
||||
|
|
||||
ns = ns.AddMembers(RewriteMethodsToProperties(ifaceDec)); |
|
||||
implNs = implNs.AddMembers(RewriteMethodsToProperties(proxy), RewriteMethodsToProperties(vtbl)); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,111 +0,0 @@ |
|||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.IO; |
|
||||
using System.Linq; |
|
||||
using MicroComGenerator.Ast; |
|
||||
using Microsoft.CodeAnalysis; |
|
||||
using Microsoft.CodeAnalysis.CSharp; |
|
||||
using Microsoft.CodeAnalysis.CSharp.Formatting; |
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax; |
|
||||
|
|
||||
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; |
|
||||
namespace MicroComGenerator |
|
||||
{ |
|
||||
public partial class CSharpGen |
|
||||
{ |
|
||||
|
|
||||
CompilationUnitSyntax Unit() |
|
||||
=> CompilationUnit().WithUsings(List(new[] |
|
||||
{ |
|
||||
"System", "System.Text", "System.Collections", "System.Collections.Generic", "Avalonia.MicroCom" |
|
||||
} |
|
||||
.Concat(_extraUsings).Select(u => UsingDirective(IdentifierName(u))))); |
|
||||
|
|
||||
string Format(CompilationUnitSyntax unit) |
|
||||
{ |
|
||||
var cw = new AdhocWorkspace(); |
|
||||
return |
|
||||
"#pragma warning disable 108\n" + |
|
||||
Microsoft.CodeAnalysis.Formatting.Formatter.Format(unit.NormalizeWhitespace(), cw, cw.Options |
|
||||
.WithChangedOption(CSharpFormattingOptions.NewLineForMembersInObjectInit, true) |
|
||||
.WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInObjectCollectionArrayInitializers, |
|
||||
true) |
|
||||
.WithChangedOption(CSharpFormattingOptions.NewLineForMembersInAnonymousTypes, true) |
|
||||
.WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInMethods, true) |
|
||||
|
|
||||
).ToFullString(); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
SyntaxToken Semicolon() => Token(SyntaxKind.SemicolonToken); |
|
||||
|
|
||||
static VariableDeclarationSyntax DeclareVar(string type, string name, |
|
||||
ExpressionSyntax initializer = null) |
|
||||
=> VariableDeclaration(ParseTypeName(type), |
|
||||
SingletonSeparatedList(VariableDeclarator(name) |
|
||||
.WithInitializer(initializer == null ? null : EqualsValueClause(initializer)))); |
|
||||
|
|
||||
FieldDeclarationSyntax DeclareConstant(string type, string name, LiteralExpressionSyntax value) |
|
||||
=> FieldDeclaration( |
|
||||
VariableDeclaration(ParseTypeName(type), |
|
||||
SingletonSeparatedList( |
|
||||
VariableDeclarator(name).WithInitializer(EqualsValueClause(value)) |
|
||||
)) |
|
||||
).WithSemicolonToken(Semicolon()) |
|
||||
.WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.ConstKeyword))); |
|
||||
|
|
||||
FieldDeclarationSyntax DeclareField(string type, string name, params SyntaxKind[] modifiers) => |
|
||||
DeclareField(type, name, null, modifiers); |
|
||||
|
|
||||
FieldDeclarationSyntax DeclareField(string type, string name, EqualsValueClauseSyntax initializer, |
|
||||
params SyntaxKind[] modifiers) => |
|
||||
FieldDeclaration( |
|
||||
VariableDeclaration(ParseTypeName(type), |
|
||||
SingletonSeparatedList( |
|
||||
VariableDeclarator(name).WithInitializer(initializer)))) |
|
||||
.WithSemicolonToken(Semicolon()) |
|
||||
.WithModifiers(TokenList(modifiers.Select(x => Token(x)))); |
|
||||
|
|
||||
bool IsPropertyRewriteCandidate(MethodDeclarationSyntax method) |
|
||||
{ |
|
||||
|
|
||||
return |
|
||||
method.ReturnType.ToFullString() != "void" |
|
||||
&& method.Identifier.Text.StartsWith("Get") |
|
||||
&& method.ParameterList.Parameters.Count == 0; |
|
||||
} |
|
||||
|
|
||||
TypeDeclarationSyntax RewriteMethodsToProperties<T>(T decl) where T : TypeDeclarationSyntax |
|
||||
{ |
|
||||
var replace = new Dictionary<MethodDeclarationSyntax, PropertyDeclarationSyntax>(); |
|
||||
foreach (var method in decl.Members.OfType<MethodDeclarationSyntax>().ToList()) |
|
||||
{ |
|
||||
if (IsPropertyRewriteCandidate(method)) |
|
||||
{ |
|
||||
var getter = AccessorDeclaration(SyntaxKind.GetAccessorDeclaration); |
|
||||
if (method.Body != null) |
|
||||
getter = getter.WithBody(method.Body); |
|
||||
else |
|
||||
getter = getter.WithSemicolonToken(Semicolon()); |
|
||||
|
|
||||
replace[method] = PropertyDeclaration(method.ReturnType, |
|
||||
method.Identifier.Text.Substring(3)) |
|
||||
.WithModifiers(method.Modifiers).AddAccessorListAccessors(getter); |
|
||||
|
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return decl.ReplaceNodes(replace.Keys, (m, m2) => replace[m]); |
|
||||
} |
|
||||
|
|
||||
bool IsInterface(string name) |
|
||||
{ |
|
||||
if (name == "IUnknown") |
|
||||
return true; |
|
||||
return _idl.Interfaces.Any(i => i.Name == name); |
|
||||
} |
|
||||
|
|
||||
private bool IsInterface(AstTypeNode type) => IsInterface(type.Name); |
|
||||
|
|
||||
} |
|
||||
} |
|
||||
@ -1,155 +0,0 @@ |
|||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Linq; |
|
||||
using System.Runtime.CompilerServices; |
|
||||
using MicroComGenerator.Ast; |
|
||||
using Microsoft.CodeAnalysis; |
|
||||
using Microsoft.CodeAnalysis.CSharp; |
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax; |
|
||||
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; |
|
||||
// ReSharper disable CoVariantArrayConversion
|
|
||||
|
|
||||
namespace MicroComGenerator |
|
||||
{ |
|
||||
public partial class CSharpGen |
|
||||
{ |
|
||||
private readonly AstIdlNode _idl; |
|
||||
private List<string> _extraUsings; |
|
||||
private string _namespace; |
|
||||
private SyntaxKind _visibility; |
|
||||
private LocalInteropHelper _localInterop = new LocalInteropHelper(); |
|
||||
|
|
||||
public CSharpGen(AstIdlNode idl) |
|
||||
{ |
|
||||
_idl = idl.Clone(); |
|
||||
new AstRewriter(_idl.Attributes.Where(a => a.Name == "clr-map") |
|
||||
.Select(x => x.Value.Trim().Split(' ')) |
|
||||
.ToDictionary(x => x[0], x => x[1]) |
|
||||
).VisitAst(_idl); |
|
||||
|
|
||||
_extraUsings = _idl.Attributes.Where(u => u.Name == "clr-using").Select(u => u.Value).ToList(); |
|
||||
_namespace = _idl.GetAttribute("clr-namespace"); |
|
||||
var visibilityString = _idl.GetAttribute("clr-access"); |
|
||||
|
|
||||
if (visibilityString == "internal") |
|
||||
_visibility = SyntaxKind.InternalKeyword; |
|
||||
else if (visibilityString == "public") |
|
||||
_visibility = SyntaxKind.PublicKeyword; |
|
||||
else |
|
||||
throw new CodeGenException("Invalid clr-access attribute"); |
|
||||
} |
|
||||
|
|
||||
class AstRewriter : AstVisitor |
|
||||
{ |
|
||||
private readonly Dictionary<string, string> _typeMap = new Dictionary<string, string>(); |
|
||||
|
|
||||
public AstRewriter(Dictionary<string, string> typeMap) |
|
||||
{ |
|
||||
_typeMap = typeMap; |
|
||||
} |
|
||||
|
|
||||
void ConvertIntPtr(AstTypeNode type) |
|
||||
{ |
|
||||
if (type.Name == "void" && type.PointerLevel > 0) |
|
||||
{ |
|
||||
type.Name = "IntPtr"; |
|
||||
type.PointerLevel--; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
protected override void VisitStructMember(AstStructMemberNode member) |
|
||||
{ |
|
||||
if (member.HasAttribute("intptr")) |
|
||||
ConvertIntPtr(member.Type); |
|
||||
base.VisitStructMember(member); |
|
||||
} |
|
||||
|
|
||||
protected override void VisitType(AstTypeNode type) |
|
||||
{ |
|
||||
if (type.IsLink) |
|
||||
{ |
|
||||
type.PointerLevel++; |
|
||||
type.IsLink = false; |
|
||||
} |
|
||||
|
|
||||
if (_typeMap.TryGetValue(type.Name, out var mapped)) |
|
||||
type.Name = mapped; |
|
||||
|
|
||||
base.VisitType(type); |
|
||||
} |
|
||||
|
|
||||
protected override void VisitArgument(AstInterfaceMemberArgumentNode argument) |
|
||||
{ |
|
||||
if (argument.HasAttribute("intptr")) |
|
||||
{ |
|
||||
if(argument.Name == "retOut") |
|
||||
Console.WriteLine(); |
|
||||
ConvertIntPtr(argument.Type); |
|
||||
} |
|
||||
|
|
||||
base.VisitArgument(argument); |
|
||||
} |
|
||||
|
|
||||
protected override void VisitInterfaceMember(AstInterfaceMemberNode member) |
|
||||
{ |
|
||||
if (member.HasAttribute("intptr")) |
|
||||
ConvertIntPtr(member.ReturnType); |
|
||||
if (member.HasAttribute("propget") && !member.Name.StartsWith("Get")) |
|
||||
member.Name = "Get" + member.Name; |
|
||||
if (member.HasAttribute("propput") && !member.Name.StartsWith("Set")) |
|
||||
member.Name = "Set" + member.Name; |
|
||||
base.VisitInterfaceMember(member); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
|
|
||||
public string Generate() |
|
||||
{ |
|
||||
var ns = NamespaceDeclaration(ParseName(_namespace)); |
|
||||
var implNs = NamespaceDeclaration(ParseName(_namespace + ".Impl"));
|
|
||||
ns = GenerateEnums(ns); |
|
||||
ns = GenerateStructs(ns); |
|
||||
foreach (var i in _idl.Interfaces) |
|
||||
GenerateInterface(ref ns, ref implNs, i); |
|
||||
|
|
||||
implNs = implNs.AddMembers(_localInterop.Class); |
|
||||
var unit = Unit().AddMembers(ns, implNs); |
|
||||
|
|
||||
return Format(unit); |
|
||||
} |
|
||||
|
|
||||
NamespaceDeclarationSyntax GenerateEnums(NamespaceDeclarationSyntax ns) |
|
||||
{ |
|
||||
return ns.AddMembers(_idl.Enums.Select(e => |
|
||||
{ |
|
||||
var dec = EnumDeclaration(e.Name) |
|
||||
.WithModifiers(TokenList(Token(_visibility))) |
|
||||
.WithMembers(SeparatedList(e.Select(m => |
|
||||
{ |
|
||||
var member = EnumMemberDeclaration(m.Name); |
|
||||
if (m.Value != null) |
|
||||
return member.WithEqualsValue(EqualsValueClause(ParseExpression(m.Value))); |
|
||||
return member; |
|
||||
}))); |
|
||||
if (e.HasAttribute("flags")) |
|
||||
dec = dec.AddAttribute("System.Flags"); |
|
||||
return dec; |
|
||||
}).ToArray()); |
|
||||
} |
|
||||
|
|
||||
NamespaceDeclarationSyntax GenerateStructs(NamespaceDeclarationSyntax ns) |
|
||||
{ |
|
||||
return ns.AddMembers(_idl.Structs.Select(e => |
|
||||
StructDeclaration(e.Name) |
|
||||
.WithModifiers(TokenList(Token(_visibility))) |
|
||||
.AddAttribute("System.Runtime.InteropServices.StructLayout", "System.Runtime.InteropServices.LayoutKind.Sequential") |
|
||||
.AddModifiers(Token(SyntaxKind.UnsafeKeyword)) |
|
||||
.WithMembers(new SyntaxList<MemberDeclarationSyntax>(SeparatedList(e.Select(m => |
|
||||
DeclareField(m.Type.ToString(), m.Name, SyntaxKind.PublicKeyword))))) |
|
||||
).ToArray()); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
|
|
||||
} |
|
||||
} |
|
||||
@ -1,119 +0,0 @@ |
|||||
using System; |
|
||||
using System.Linq; |
|
||||
using System.Text; |
|
||||
using MicroComGenerator.Ast; |
|
||||
|
|
||||
namespace MicroComGenerator |
|
||||
{ |
|
||||
public class CppGen |
|
||||
{ |
|
||||
static string ConvertType(AstTypeNode type) |
|
||||
{ |
|
||||
var name = type.Name; |
|
||||
if (name == "byte") |
|
||||
name = "unsigned char"; |
|
||||
else if(name == "uint") |
|
||||
name = "unsigned int"; |
|
||||
|
|
||||
type = type.Clone(); |
|
||||
type.Name = name; |
|
||||
return type.Format(); |
|
||||
} |
|
||||
|
|
||||
public static string GenerateCpp(AstIdlNode idl) |
|
||||
{ |
|
||||
var sb = new StringBuilder(); |
|
||||
var preamble = idl.GetAttributeOrDefault("cpp-preamble"); |
|
||||
if (preamble != null) |
|
||||
sb.AppendLine(preamble); |
|
||||
|
|
||||
foreach (var s in idl.Structs) |
|
||||
sb.AppendLine("struct " + s.Name + ";"); |
|
||||
|
|
||||
foreach (var s in idl.Interfaces) |
|
||||
sb.AppendLine("struct " + s.Name + ";"); |
|
||||
|
|
||||
foreach (var en in idl.Enums) |
|
||||
{ |
|
||||
sb.Append("enum "); |
|
||||
if (en.Attributes.Any(a => a.Name == "class-enum")) |
|
||||
sb.Append("class "); |
|
||||
sb.AppendLine(en.Name).AppendLine("{"); |
|
||||
|
|
||||
foreach (var m in en) |
|
||||
{ |
|
||||
sb.Append(" ").Append(m.Name); |
|
||||
if (m.Value != null) |
|
||||
sb.Append(" = ").Append(m.Value); |
|
||||
sb.AppendLine(","); |
|
||||
} |
|
||||
|
|
||||
sb.AppendLine("};"); |
|
||||
} |
|
||||
|
|
||||
foreach (var s in idl.Structs) |
|
||||
{ |
|
||||
sb.Append("struct ").AppendLine(s.Name).AppendLine("{"); |
|
||||
foreach (var m in s) |
|
||||
sb.Append(" ").Append(ConvertType(m.Type)).Append(" ").Append(m.Name).AppendLine(";"); |
|
||||
|
|
||||
sb.AppendLine("};"); |
|
||||
} |
|
||||
|
|
||||
foreach (var i in idl.Interfaces) |
|
||||
{ |
|
||||
var guidString = i.GetAttribute("uuid"); |
|
||||
var guid = Guid.Parse(guidString).ToString().Replace("-", ""); |
|
||||
|
|
||||
|
|
||||
sb.Append("COMINTERFACE(").Append(i.Name).Append(", ") |
|
||||
.Append(guid.Substring(0, 8)).Append(", ") |
|
||||
.Append(guid.Substring(8, 4)).Append(", ") |
|
||||
.Append(guid.Substring(12, 4)); |
|
||||
for (var c = 0; c < 8; c++) |
|
||||
{ |
|
||||
sb.Append(", ").Append(guid.Substring(16 + c * 2, 2)); |
|
||||
} |
|
||||
|
|
||||
sb.Append(") : "); |
|
||||
if (i.HasAttribute("cpp-virtual-inherits")) |
|
||||
sb.Append("virtual "); |
|
||||
sb.AppendLine(i.Inherits ?? "IUnknown") |
|
||||
.AppendLine("{"); |
|
||||
|
|
||||
foreach (var m in i) |
|
||||
{ |
|
||||
sb.Append(" ") |
|
||||
.Append("virtual ") |
|
||||
.Append(ConvertType(m.ReturnType)) |
|
||||
.Append(" ").Append(m.Name).Append(" ("); |
|
||||
if (m.Count == 0) |
|
||||
sb.AppendLine(") = 0;"); |
|
||||
else |
|
||||
{ |
|
||||
sb.AppendLine(); |
|
||||
for (var c = 0; c < m.Count; c++) |
|
||||
{ |
|
||||
var arg = m[c]; |
|
||||
sb.Append(" "); |
|
||||
if (arg.Attributes.Any(a => a.Name == "const")) |
|
||||
sb.Append("const "); |
|
||||
sb.Append(ConvertType(arg.Type)) |
|
||||
.Append(" ") |
|
||||
.Append(arg.Name); |
|
||||
if (c != m.Count - 1) |
|
||||
sb.Append(", "); |
|
||||
sb.AppendLine(); |
|
||||
} |
|
||||
|
|
||||
sb.AppendLine(" ) = 0;"); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
sb.AppendLine("};"); |
|
||||
} |
|
||||
|
|
||||
return sb.ToString(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,97 +0,0 @@ |
|||||
|
|
||||
using System; |
|
||||
using System.Linq; |
|
||||
using Microsoft.CodeAnalysis; |
|
||||
using Microsoft.CodeAnalysis.CSharp; |
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax; |
|
||||
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; |
|
||||
namespace MicroComGenerator |
|
||||
{ |
|
||||
public static class Extensions |
|
||||
{ |
|
||||
public static ClassDeclarationSyntax AddModifiers(this ClassDeclarationSyntax cl, params SyntaxKind[] modifiers) |
|
||||
{ |
|
||||
if (modifiers == null) |
|
||||
return cl; |
|
||||
return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray()); |
|
||||
} |
|
||||
|
|
||||
public static MethodDeclarationSyntax AddModifiers(this MethodDeclarationSyntax cl, params SyntaxKind[] modifiers) |
|
||||
{ |
|
||||
if (modifiers == null) |
|
||||
return cl; |
|
||||
return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray()); |
|
||||
} |
|
||||
|
|
||||
public static PropertyDeclarationSyntax AddModifiers(this PropertyDeclarationSyntax cl, params SyntaxKind[] modifiers) |
|
||||
{ |
|
||||
if (modifiers == null) |
|
||||
return cl; |
|
||||
return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray()); |
|
||||
} |
|
||||
|
|
||||
public static ConstructorDeclarationSyntax AddModifiers(this ConstructorDeclarationSyntax cl, params SyntaxKind[] modifiers) |
|
||||
{ |
|
||||
if (modifiers == null) |
|
||||
return cl; |
|
||||
return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray()); |
|
||||
} |
|
||||
|
|
||||
public static AccessorDeclarationSyntax AddModifiers(this AccessorDeclarationSyntax cl, params SyntaxKind[] modifiers) |
|
||||
{ |
|
||||
if (modifiers == null) |
|
||||
return cl; |
|
||||
return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray()); |
|
||||
} |
|
||||
|
|
||||
public static string WithLowerFirst(this string s) |
|
||||
{ |
|
||||
if (string.IsNullOrEmpty(s)) |
|
||||
return s; |
|
||||
return char.ToLowerInvariant(s[0]) + s.Substring(1); |
|
||||
} |
|
||||
|
|
||||
public static ExpressionSyntax MemberAccess(params string[] identifiers) |
|
||||
{ |
|
||||
if (identifiers == null || identifiers.Length == 0) |
|
||||
throw new ArgumentException(); |
|
||||
var expr = (ExpressionSyntax)IdentifierName(identifiers[0]); |
|
||||
for (var c = 1; c < identifiers.Length; c++) |
|
||||
expr = MemberAccess(expr, identifiers[c]); |
|
||||
return expr; |
|
||||
} |
|
||||
|
|
||||
public static ExpressionSyntax MemberAccess(ExpressionSyntax expr, params string[] identifiers) |
|
||||
{ |
|
||||
foreach (var i in identifiers) |
|
||||
expr = MemberAccess(expr, i); |
|
||||
return expr; |
|
||||
} |
|
||||
|
|
||||
public static MemberAccessExpressionSyntax MemberAccess(ExpressionSyntax expr, string identifier) => |
|
||||
MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, expr, IdentifierName(identifier)); |
|
||||
|
|
||||
public static ClassDeclarationSyntax WithBaseType(this ClassDeclarationSyntax cl, string bt) |
|
||||
{ |
|
||||
return cl.AddBaseListTypes(SimpleBaseType(SyntaxFactory.ParseTypeName(bt))); |
|
||||
} |
|
||||
|
|
||||
public static InterfaceDeclarationSyntax WithBaseType(this InterfaceDeclarationSyntax cl, string bt) |
|
||||
{ |
|
||||
return cl.AddBaseListTypes(SimpleBaseType(SyntaxFactory.ParseTypeName(bt))); |
|
||||
} |
|
||||
|
|
||||
public static T AddAttribute<T>(this T member, string attribute, params string[] args) where T : MemberDeclarationSyntax |
|
||||
{ |
|
||||
return (T)member.AddAttributeLists(AttributeList(SingletonSeparatedList( |
|
||||
Attribute(ParseName(attribute), AttributeArgumentList( |
|
||||
SeparatedList(args.Select(a => AttributeArgument(ParseExpression(a))))))))); |
|
||||
} |
|
||||
|
|
||||
public static string StripPrefix(this string s, string prefix) => string.IsNullOrEmpty(s) |
|
||||
? s |
|
||||
: s.StartsWith(prefix) |
|
||||
? s.Substring(prefix.Length) |
|
||||
: s; |
|
||||
} |
|
||||
} |
|
||||
@ -1,10 +0,0 @@ |
|||||
<Project Sdk="Microsoft.NET.Sdk"> |
|
||||
<PropertyGroup> |
|
||||
<OutputType>Exe</OutputType> |
|
||||
<TargetFramework>net6.0</TargetFramework> |
|
||||
</PropertyGroup> |
|
||||
<ItemGroup> |
|
||||
<PackageReference Include="CommandLineParser" Version="2.8.0" /> |
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.7.0" /> |
|
||||
</ItemGroup> |
|
||||
</Project> |
|
||||
@ -1,27 +0,0 @@ |
|||||
using System; |
|
||||
|
|
||||
namespace MicroComGenerator |
|
||||
{ |
|
||||
class ParseException : Exception |
|
||||
{ |
|
||||
public int Line { get; } |
|
||||
public int Position { get; } |
|
||||
|
|
||||
public ParseException(string message, int line, int position) : base($"({line}, {position}) {message}") |
|
||||
{ |
|
||||
Line = line; |
|
||||
Position = position; |
|
||||
} |
|
||||
|
|
||||
public ParseException(string message, ref TokenParser parser) : this(message, parser.Line, parser.Position) |
|
||||
{ |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
class CodeGenException : Exception |
|
||||
{ |
|
||||
public CodeGenException(string message) : base(message) |
|
||||
{ |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,52 +0,0 @@ |
|||||
using System; |
|
||||
using System.IO; |
|
||||
using System.Linq; |
|
||||
using CommandLine; |
|
||||
|
|
||||
namespace MicroComGenerator |
|
||||
{ |
|
||||
class Program |
|
||||
{ |
|
||||
public class Options |
|
||||
{ |
|
||||
[Option('i', "input", Required = true, HelpText = "Input IDL file")] |
|
||||
public string Input { get; set; } |
|
||||
|
|
||||
[Option("cpp", Required = false, HelpText = "C++ output file")] |
|
||||
public string CppOutput { get; set; } |
|
||||
|
|
||||
[Option("cs", Required = false, HelpText = "C# output file")] |
|
||||
public string CSharpOutput { get; set; } |
|
||||
|
|
||||
} |
|
||||
|
|
||||
static int Main(string[] args) |
|
||||
{ |
|
||||
var p = Parser.Default.ParseArguments<Options>(args); |
|
||||
if (p is NotParsed<Options>) |
|
||||
{ |
|
||||
return 1; |
|
||||
} |
|
||||
|
|
||||
var opts = ((Parsed<Options>)p).Value; |
|
||||
|
|
||||
var text = File.ReadAllText(opts.Input); |
|
||||
var ast = AstParser.Parse(text); |
|
||||
|
|
||||
if (opts.CppOutput != null) |
|
||||
File.WriteAllText(opts.CppOutput, CppGen.GenerateCpp(ast)); |
|
||||
|
|
||||
if (opts.CSharpOutput != null) |
|
||||
{ |
|
||||
File.WriteAllText(opts.CSharpOutput, new CSharpGen(ast).Generate()); |
|
||||
|
|
||||
// HACK: Can't work out how to get the VS project system's fast up-to-date checks
|
|
||||
// to ignore the generated code, so as a workaround set the write time to that of
|
|
||||
// the input.
|
|
||||
File.SetLastWriteTime(opts.CSharpOutput, File.GetLastWriteTime(opts.Input)); |
|
||||
} |
|
||||
|
|
||||
return 0; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,417 +0,0 @@ |
|||||
using System; |
|
||||
using System.Globalization; |
|
||||
using System.IO; |
|
||||
|
|
||||
namespace MicroComGenerator |
|
||||
{ |
|
||||
internal ref struct TokenParser |
|
||||
{ |
|
||||
private ReadOnlySpan<char> _s; |
|
||||
public int Position { get; private set; } |
|
||||
public int Line { get; private set; } |
|
||||
public TokenParser(ReadOnlySpan<char> s) |
|
||||
{ |
|
||||
_s = s; |
|
||||
Position = 0; |
|
||||
Line = 0; |
|
||||
} |
|
||||
|
|
||||
public void SkipWhitespace() |
|
||||
{ |
|
||||
while (true) |
|
||||
{ |
|
||||
if(_s.Length == 0) |
|
||||
return; |
|
||||
if (char.IsWhiteSpace(_s[0])) |
|
||||
Advance(1); |
|
||||
else if (_s[0] == '/' && _s.Length>1) |
|
||||
{ |
|
||||
if (_s[1] == '/') |
|
||||
SkipOneLineComment(); |
|
||||
else if (_s[1] == '*') |
|
||||
SkipMultiLineComment(); |
|
||||
else |
|
||||
return; |
|
||||
} |
|
||||
else |
|
||||
return; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
void SkipOneLineComment() |
|
||||
{ |
|
||||
while (true) |
|
||||
{ |
|
||||
if (_s.Length > 0 && _s[0] != '\n' && _s[0] != '\r') |
|
||||
Advance(1); |
|
||||
else |
|
||||
return; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
void SkipMultiLineComment() |
|
||||
{ |
|
||||
var l = Line; |
|
||||
var p = Position; |
|
||||
while (true) |
|
||||
{ |
|
||||
if (_s.Length == 0) |
|
||||
throw new ParseException("No matched */ found for /*", l, p); |
|
||||
|
|
||||
if (_s[0] == '*' && _s.Length > 1 && _s[1] == '/') |
|
||||
{ |
|
||||
Advance(2); |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
Advance(1); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
static bool IsAlphaNumeric(char ch) => (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || |
|
||||
(ch >= 'A' && ch <= 'Z'); |
|
||||
|
|
||||
public void Consume(char c) |
|
||||
{ |
|
||||
if (!TryConsume(c)) |
|
||||
throw new ParseException("Expected " + c, Line, Position); |
|
||||
} |
|
||||
public bool TryConsume(char c) |
|
||||
{ |
|
||||
SkipWhitespace(); |
|
||||
if (_s.Length == 0 || _s[0] != c) |
|
||||
return false; |
|
||||
|
|
||||
Advance(1); |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
public bool TryConsume(string s) |
|
||||
{ |
|
||||
SkipWhitespace(); |
|
||||
if (_s.Length < s.Length) |
|
||||
return false; |
|
||||
for (var c = 0; c < s.Length; c++) |
|
||||
{ |
|
||||
if (_s[c] != s[c]) |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
Advance(s.Length); |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
public bool TryConsumeAny(ReadOnlySpan<char> chars, out char token) |
|
||||
{ |
|
||||
SkipWhitespace(); |
|
||||
token = default; |
|
||||
if (_s.Length == 0) |
|
||||
return false; |
|
||||
|
|
||||
foreach (var c in chars) |
|
||||
{ |
|
||||
if (c == _s[0]) |
|
||||
{ |
|
||||
token = c; |
|
||||
Advance(1); |
|
||||
return true; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
|
|
||||
public bool TryParseKeyword(string keyword) |
|
||||
{ |
|
||||
SkipWhitespace(); |
|
||||
if (keyword.Length > _s.Length) |
|
||||
return false; |
|
||||
for(var c=0; c<keyword.Length;c++) |
|
||||
if (keyword[c] != _s[c]) |
|
||||
return false; |
|
||||
|
|
||||
if (_s.Length > keyword.Length && IsAlphaNumeric(_s[keyword.Length])) |
|
||||
return false; |
|
||||
|
|
||||
Advance(keyword.Length); |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
public bool TryParseKeywordLowerCase(string keywordInLowerCase) |
|
||||
{ |
|
||||
SkipWhitespace(); |
|
||||
if (keywordInLowerCase.Length > _s.Length) |
|
||||
return false; |
|
||||
for(var c=0; c<keywordInLowerCase.Length;c++) |
|
||||
if (keywordInLowerCase[c] != char.ToLowerInvariant(_s[c])) |
|
||||
return false; |
|
||||
|
|
||||
if (_s.Length > keywordInLowerCase.Length && IsAlphaNumeric(_s[keywordInLowerCase.Length])) |
|
||||
return false; |
|
||||
|
|
||||
Advance(keywordInLowerCase.Length); |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
public void Advance(int c) |
|
||||
{ |
|
||||
while (c > 0) |
|
||||
{ |
|
||||
if (_s[0] == '\n') |
|
||||
{ |
|
||||
Line++; |
|
||||
Position = 0; |
|
||||
} |
|
||||
else |
|
||||
Position++; |
|
||||
|
|
||||
_s = _s.Slice(1); |
|
||||
c--; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public int Length => _s.Length; |
|
||||
public bool Eof |
|
||||
{ |
|
||||
get |
|
||||
{ |
|
||||
SkipWhitespace(); |
|
||||
return Length == 0; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public char Peek |
|
||||
{ |
|
||||
get |
|
||||
{ |
|
||||
if (_s.Length == 0) |
|
||||
throw new ParseException("Unexpected EOF", Line, Position); |
|
||||
return _s[0]; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public string ParseIdentifier(ReadOnlySpan<char> extraValidChars) |
|
||||
{ |
|
||||
if (!TryParseIdentifier(extraValidChars, out var ident)) |
|
||||
throw new ParseException("Identifier expected", Line, Position); |
|
||||
return ident.ToString(); |
|
||||
} |
|
||||
|
|
||||
public string ParseIdentifier() |
|
||||
{ |
|
||||
if (!TryParseIdentifier(out var ident)) |
|
||||
throw new ParseException("Identifier expected", Line, Position); |
|
||||
return ident.ToString(); |
|
||||
} |
|
||||
|
|
||||
public bool TryParseIdentifier(ReadOnlySpan<char> extraValidChars, out ReadOnlySpan<char> res) |
|
||||
{ |
|
||||
res = ReadOnlySpan<char>.Empty; |
|
||||
SkipWhitespace(); |
|
||||
if (_s.Length == 0) |
|
||||
return false; |
|
||||
var first = _s[0]; |
|
||||
if (!((first >= 'a' && first <= 'z') || (first >= 'A' && first <= 'Z') || first == '_')) |
|
||||
return false; |
|
||||
int len = 1; |
|
||||
for (var c = 1; c < _s.Length; c++) |
|
||||
{ |
|
||||
var ch = _s[c]; |
|
||||
if (IsAlphaNumeric(ch) || ch == '_') |
|
||||
len++; |
|
||||
else |
|
||||
{ |
|
||||
var found = false; |
|
||||
foreach(var vc in extraValidChars) |
|
||||
if (vc == ch) |
|
||||
{ |
|
||||
found = true; |
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
if (found) |
|
||||
len++; |
|
||||
else |
|
||||
break; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
res = _s.Slice(0, len); |
|
||||
Advance(len); |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
public bool TryParseIdentifier(out ReadOnlySpan<char> res) |
|
||||
{ |
|
||||
res = ReadOnlySpan<char>.Empty; |
|
||||
SkipWhitespace(); |
|
||||
if (_s.Length == 0) |
|
||||
return false; |
|
||||
var first = _s[0]; |
|
||||
if (!((first >= 'a' && first <= 'z') || (first >= 'A' && first <= 'Z') || first == '_')) |
|
||||
return false; |
|
||||
int len = 1; |
|
||||
for (var c = 1; c < _s.Length; c++) |
|
||||
{ |
|
||||
var ch = _s[c]; |
|
||||
if (IsAlphaNumeric(ch) || ch == '_') |
|
||||
len++; |
|
||||
else |
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
res = _s.Slice(0, len); |
|
||||
Advance(len); |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
public string ReadToEol() |
|
||||
{ |
|
||||
var initial = _s; |
|
||||
var len = 0; |
|
||||
while (true) |
|
||||
{ |
|
||||
if (_s.Length > 0 && _s[0] != '\n' && _s[0] != '\r') |
|
||||
{ |
|
||||
len++; |
|
||||
Advance(1); |
|
||||
} |
|
||||
else |
|
||||
return initial.Slice(0, len).ToString(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public string ReadTo(char c) |
|
||||
{ |
|
||||
var initial = _s; |
|
||||
var len = 0; |
|
||||
var l = Line; |
|
||||
var p = Position; |
|
||||
while (true) |
|
||||
{ |
|
||||
if (_s.Length == 0) |
|
||||
throw new ParseException("Expected " + c + " before EOF", l, p); |
|
||||
|
|
||||
if (_s[0] != c) |
|
||||
{ |
|
||||
len++; |
|
||||
Advance(1); |
|
||||
} |
|
||||
else |
|
||||
return initial.Slice(0, len).ToString(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public string ReadToAny(ReadOnlySpan<char> chars) |
|
||||
{ |
|
||||
var initial = _s; |
|
||||
var len = 0; |
|
||||
var l = Line; |
|
||||
var p = Position; |
|
||||
while (true) |
|
||||
{ |
|
||||
if (_s.Length == 0) |
|
||||
throw new ParseException("Expected any of '" + chars.ToString() + "' before EOF", l, p); |
|
||||
|
|
||||
var foundTerminator = false; |
|
||||
foreach (var term in chars) |
|
||||
{ |
|
||||
if (_s[0] == term) |
|
||||
{ |
|
||||
foundTerminator = true; |
|
||||
break; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (!foundTerminator) |
|
||||
{ |
|
||||
len++; |
|
||||
Advance(1); |
|
||||
} |
|
||||
else |
|
||||
return initial.Slice(0, len).ToString(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public bool TryParseCall(out ReadOnlySpan<char> res) |
|
||||
{ |
|
||||
res = ReadOnlySpan<char>.Empty; |
|
||||
SkipWhitespace(); |
|
||||
if (_s.Length == 0) |
|
||||
return false; |
|
||||
var first = _s[0]; |
|
||||
if (!((first >= 'a' && first <= 'z') || (first >= 'A' && first <= 'Z'))) |
|
||||
return false; |
|
||||
int len = 1; |
|
||||
for (var c = 1; c < _s.Length; c++) |
|
||||
{ |
|
||||
var ch = _s[c]; |
|
||||
if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch<= 'Z') || ch == '.') |
|
||||
len++; |
|
||||
else |
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
res = _s.Slice(0, len); |
|
||||
|
|
||||
// Find '('
|
|
||||
for (var c = len; c < _s.Length; c++) |
|
||||
{ |
|
||||
if(char.IsWhiteSpace(_s[c])) |
|
||||
continue; |
|
||||
if(_s[c]=='(') |
|
||||
{ |
|
||||
Advance(c + 1); |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
return false; |
|
||||
|
|
||||
} |
|
||||
|
|
||||
return false; |
|
||||
|
|
||||
} |
|
||||
|
|
||||
|
|
||||
public bool TryParseFloat(out float res) |
|
||||
{ |
|
||||
res = 0; |
|
||||
SkipWhitespace(); |
|
||||
if (_s.Length == 0) |
|
||||
return false; |
|
||||
|
|
||||
var len = 0; |
|
||||
var dotCount = 0; |
|
||||
for (var c = 0; c < _s.Length; c++) |
|
||||
{ |
|
||||
var ch = _s[c]; |
|
||||
if (ch >= '0' && ch <= '9') |
|
||||
len = c + 1; |
|
||||
else if (ch == '.' && dotCount == 0) |
|
||||
{ |
|
||||
len = c + 1; |
|
||||
dotCount++; |
|
||||
} |
|
||||
else |
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
var span = _s.Slice(0, len); |
|
||||
|
|
||||
#if NETSTANDARD2_0
|
|
||||
if (!float.TryParse(span.ToString(), NumberStyles.Number, CultureInfo.InvariantCulture, out res)) |
|
||||
return false; |
|
||||
#else
|
|
||||
if (!float.TryParse(span, NumberStyles.Number, CultureInfo.InvariantCulture, out res)) |
|
||||
return false; |
|
||||
#endif
|
|
||||
Advance(len); |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
public override string ToString() => _s.ToString(); |
|
||||
|
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue