Browse Source

Merge pull request #7259 from AvaloniaUI/features/use-external-microcom-generator

Use microcom generator from nuget
# Conflicts:
#	build/MicroCom.targets
release/0.10.13
Nikita Tsukanov 4 years ago
committed by Dan Walmsley
parent
commit
480dfdfe0d
  1. 27
      Avalonia.sln
  2. 6
      azure-pipelines.yml
  3. 34
      build/MicroCom.targets
  4. 8
      nukebuild/MicroComGen.cs
  5. 6
      nukebuild/_build.csproj
  6. 6
      src/Avalonia.MicroCom/MicroComRuntime.cs
  7. 5
      src/Avalonia.MicroCom/MicroComVtblBase.cs
  8. 5
      src/Avalonia.Native/Avalonia.Native.csproj
  9. 6
      src/Directory.Build.props
  10. 10
      src/Shared/ModuleInitializer.cs
  11. 7
      src/Windows/Avalonia.Win32/Avalonia.Win32.csproj
  12. 241
      src/tools/MicroComGenerator/Ast.cs
  13. 232
      src/tools/MicroComGenerator/AstParser.cs
  14. 484
      src/tools/MicroComGenerator/CSharpGen.InterfaceGen.cs
  15. 111
      src/tools/MicroComGenerator/CSharpGen.Utils.cs
  16. 155
      src/tools/MicroComGenerator/CSharpGen.cs
  17. 119
      src/tools/MicroComGenerator/CppGen.cs
  18. 97
      src/tools/MicroComGenerator/Extensions.cs
  19. 10
      src/tools/MicroComGenerator/MicroComGenerator.csproj
  20. 27
      src/tools/MicroComGenerator/ParseException.cs
  21. 52
      src/tools/MicroComGenerator/Program.cs
  22. 417
      src/tools/MicroComGenerator/TokenParser.cs

27
Avalonia.sln

@ -223,8 +223,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI.Events"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sandbox", "samples\Sandbox\Sandbox.csproj", "{11BE52AF-E2DD-4CF0-B19A-05285ACAF571}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MicroComGenerator", "src\tools\MicroComGenerator\MicroComGenerator.csproj", "{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.MicroCom", "src\Avalonia.MicroCom\Avalonia.MicroCom.csproj", "{FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MiniMvvm", "samples\MiniMvvm\MiniMvvm.csproj", "{BC594FD5-4AF2-409E-A1E6-04123F54D7C5}"
@ -2029,30 +2027,6 @@ Global
{11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Release|iPhone.Build.0 = Release|Any CPU
{11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|iPhone.Build.0 = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|iPhone.Build.0 = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|Any CPU.Build.0 = Release|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|iPhone.ActiveCfg = Release|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|iPhone.Build.0 = Release|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
@ -2255,7 +2229,6 @@ Global
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
{909A8CBD-7D0E-42FD-B841-022AD8925820} = {8B6A8209-894F-4BA1-B880-965FD453982C}
{11BE52AF-E2DD-4CF0-B19A-05285ACAF571} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
{BC594FD5-4AF2-409E-A1E6-04123F54D7C5} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{25831348-EB2A-483E-9576-E8F6528674A5} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268}
{C08E9894-AA92-426E-BF56-033E262CAD3E} = {9B9E3891-2366-4253-A952-D08BCEB71098}

6
azure-pipelines.yml

@ -58,8 +58,10 @@ jobs:
displayName: 'Generate avalonia-native'
inputs:
script: |
export PATH="`pwd`/sdk:$PATH"
cd src/tools/MicroComGenerator; dotnet run -f net6.0 -i ../../Avalonia.Native/avn.idl --cpp ../../../native/Avalonia.Native/inc/avalonia-native.h
export COREHOST_TRACE=0
export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
export DOTNET_CLI_TELEMETRY_OPTOUT=1
./build.sh --target GenerateCppHeaders --configuration Release
- task: Xcode@5
inputs:

34
build/MicroCom.targets

@ -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 &quot;$(MSBuildThisFileDirectory)../src/tools/MicroComGenerator/bin/$(Configuration)/net6.0/MicroComGenerator.dll&quot; -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>

8
nukebuild/MicroComGen.cs

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

6
nukebuild/_build.csproj

@ -15,7 +15,7 @@
<PackageReference Include="JetBrains.dotMemoryUnit" Version="3.0.20171219.105559" />
<PackageReference Include="vswhere" Version="2.6.7" Condition=" '$(OS)' == 'Windows_NT' " />
<PackageReference Include="ILRepack.NETStandard" Version="2.0.4" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.7.0" />
<PackageReference Include="MicroCom.CodeGenerator" Version="0.10.4" />
<!-- Keep in sync with Avalonia.Build.Tasks -->
<PackageReference Include="Mono.Cecil" Version="0.11.2" />
<PackageReference Include="Pharmacist.Core" Version="1.8.1" />
@ -38,10 +38,6 @@
<None Include="..\GitVersion.yml" Condition="Exists('..\GitVersion.yml')" />
<Compile Remove="Numerge/**/*.*" />
<Compile Include="Numerge/Numerge/**/*.cs" />
<Compile Include="..\src\tools\MicroComGenerator\**\*.cs" Exclude="..\src\tools\MicroComGenerator\obj\**">
<Link>MicroComGenerator\%(Filename)%(Extension)</Link>
</Compile>
<Compile Remove="..\src\tools\MicroComGenerator\Program.cs" />
</ItemGroup>
</Project>

6
src/Avalonia.MicroCom/MicroComRuntime.cs

@ -36,7 +36,13 @@ namespace Avalonia.MicroCom
public static T CreateProxyFor<T>(void* pObject, bool ownsHandle) => (T)CreateProxyFor(typeof(T), new IntPtr(pObject), ownsHandle);
public static T CreateProxyFor<T>(IntPtr pObject, bool ownsHandle) => (T)CreateProxyFor(typeof(T), pObject, ownsHandle);
public static T CreateProxyOrNullFor<T>(void* pObject, bool ownsHandle) where T : class =>
pObject == null ? null : (T)CreateProxyFor(typeof(T), new IntPtr(pObject), ownsHandle);
public static T CreateProxyOrNullFor<T>(IntPtr pObject, bool ownsHandle) where T : class =>
pObject == IntPtr.Zero ? null : (T)CreateProxyFor(typeof(T), pObject, ownsHandle);
public static object CreateProxyFor(Type type, IntPtr pObject, bool ownsHandle) => _factories[type](pObject, ownsHandle);
public static IntPtr GetNativeIntPtr<T>(this T obj, bool owned = false) where T : IUnknown

5
src/Avalonia.MicroCom/MicroComVtblBase.cs

@ -21,6 +21,11 @@ namespace Avalonia.MicroCom
AddMethod((AddRefDelegate)Release);
}
protected void AddMethod(void* f)
{
_methods.Add(new IntPtr(f));
}
protected void AddMethod(Delegate d)
{
GCHandle.Alloc(d);

5
src/Avalonia.Native/Avalonia.Native.csproj

@ -6,6 +6,7 @@
<IsPackable Condition="'$([MSBuild]::IsOSPlatform(OSX))' == 'True'">true</IsPackable>
<TargetFramework>netstandard2.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<MicroComGeneratorRuntimeNamespace>Avalonia.MicroCom</MicroComGeneratorRuntimeNamespace>
</PropertyGroup>
<ItemGroup Condition="'$(PackAvaloniaNative)' == 'true'">
@ -20,7 +21,7 @@
<ItemGroup>
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" />
<ProjectReference Include="..\Avalonia.Dialogs\Avalonia.Dialogs.csproj" />
<AvnComIdl Include="avn.idl" OutputFile="Interop.Generated.cs" />
<PackageReference Include="MicroCom.CodeGenerator.MSBuild" Version="0.10.4" PrivateAssets="all" />
<MicroComIdl Include="avn.idl" CSharpInteropPath="Interop.Generated.cs" />
</ItemGroup>
<Import Project="../../build/MicroCom.targets" />
</Project>

6
src/Directory.Build.props

@ -2,4 +2,10 @@
<Import Project="..\Directory.Build.props" />
<Import Project="..\build\SharedVersion.props" />
<Import Project="..\build\SourceLink.props" Condition="'$(DisableSourceLink)' == ''" />
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)\Shared\ModuleInitializer.cs" >
<Link>Shared\_ModuleInitializer.cs</Link>
<Visible>false</Visible>
</Compile>
</ItemGroup>
</Project>

10
src/Shared/ModuleInitializer.cs

@ -0,0 +1,10 @@
namespace System.Runtime.CompilerServices
{
#if !NET5_0_OR_GREATER
internal class ModuleInitializerAttribute : Attribute
{
}
#endif
}

7
src/Windows/Avalonia.Win32/Avalonia.Win32.csproj

@ -3,14 +3,15 @@
<TargetFramework>netstandard2.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PackageId>Avalonia.Win32</PackageId>
<MicroComGeneratorRuntimeNamespace>Avalonia.MicroCom</MicroComGeneratorRuntimeNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
<ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" />
<PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2020091801" />
<AvnComIdl Include="WinRT\winrt.idl" OutputFile="WinRT\WinRT.Generated.cs" />
<AvnComIdl Include="Win32Com\win32.idl" OutputFile="Win32Com\Win32.Generated.cs" />
<PackageReference Include="MicroCom.CodeGenerator.MSBuild" Version="0.10.4" PrivateAssets="all" />
<MicroComIdl Include="WinRT\winrt.idl" CSharpInteropPath="WinRT\WinRT.Generated.cs" />
<MicroComIdl Include="Win32Com\win32.idl" CSharpInteropPath="Win32Com\Win32.Generated.cs" />
</ItemGroup>
<Import Project="../../../build/MicroCom.targets" />
<Import Project="$(MSBuildThisFileDirectory)\..\..\..\build\System.Drawing.Common.props" />
</Project>

241
src/tools/MicroComGenerator/Ast.cs

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

232
src/tools/MicroComGenerator/AstParser.cs

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

484
src/tools/MicroComGenerator/CSharpGen.InterfaceGen.cs

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

111
src/tools/MicroComGenerator/CSharpGen.Utils.cs

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

155
src/tools/MicroComGenerator/CSharpGen.cs

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

119
src/tools/MicroComGenerator/CppGen.cs

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

97
src/tools/MicroComGenerator/Extensions.cs

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

10
src/tools/MicroComGenerator/MicroComGenerator.csproj

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

27
src/tools/MicroComGenerator/ParseException.cs

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

52
src/tools/MicroComGenerator/Program.cs

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

417
src/tools/MicroComGenerator/TokenParser.cs

@ -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…
Cancel
Save