Browse Source
Use microcom generator from nuget # Conflicts: # build/MicroCom.targetsrelease/0.10.13
committed by
Dan Walmsley
22 changed files with 43 additions and 2022 deletions
@ -1,34 +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> |
|||
</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 MicroComGenerator; |
|||
using MicroCom.CodeGenerator; |
|||
using Nuke.Common; |
|||
|
|||
partial class Build : NukeBuild |
|||
{ |
|||
Target GenerateCppHeaders => _ => _.Executes(() => |
|||
{ |
|||
var text = File.ReadAllText(RootDirectory / "src" / "Avalonia.Native" / "avn.idl"); |
|||
var ast = AstParser.Parse(text); |
|||
var file = MicroComCodeGenerator.Parse( |
|||
File.ReadAllText(RootDirectory / "src" / "Avalonia.Native" / "avn.idl")); |
|||
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