28 changed files with 2821 additions and 10 deletions
@ -0,0 +1,130 @@ |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Reflection; |
|||
using Mono.Cecil; |
|||
using Mono.Cecil.Cil; |
|||
using XamlX.TypeSystem; |
|||
using MethodAttributes = Mono.Cecil.MethodAttributes; |
|||
|
|||
namespace Avalonia.Build.Tasks |
|||
{ |
|||
static class ComInteropHelper |
|||
{ |
|||
public static void PatchAssembly(AssemblyDefinition asm, CecilTypeSystem typeSystem) |
|||
{ |
|||
var classToRemoveList = new List<TypeDefinition>(); |
|||
var initializers = new List<MethodDefinition>(); |
|||
foreach (var type in asm.MainModule.Types) |
|||
{ |
|||
var i = type.Methods.FirstOrDefault(m => m.Name == "__MicroComModuleInit"); |
|||
if (i != null) |
|||
initializers.Add(i); |
|||
|
|||
PatchType(type, classToRemoveList); |
|||
} |
|||
|
|||
// Remove All Interop classes
|
|||
foreach (var type in classToRemoveList) |
|||
asm.MainModule.Types.Remove(type); |
|||
|
|||
|
|||
// Patch automatic registrations
|
|||
if (initializers.Count != 0) |
|||
{ |
|||
var moduleType = asm.MainModule.Types.First(x => x.Name == "<Module>"); |
|||
|
|||
// Needed for compatibility with upcoming .NET 5 feature, look for existing initializer first
|
|||
var staticCtor = moduleType.Methods.FirstOrDefault(m => m.Name == ".cctor"); |
|||
if (staticCtor == null) |
|||
{ |
|||
// Create a new static ctor if none exists
|
|||
staticCtor = new MethodDefinition(".cctor", |
|||
MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName | |
|||
MethodAttributes.Static | MethodAttributes.Private, |
|||
asm.MainModule.TypeSystem.Void); |
|||
staticCtor.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); |
|||
moduleType.Methods.Add(staticCtor); |
|||
} |
|||
|
|||
foreach (var i in initializers) |
|||
staticCtor.Body.Instructions.Add(Instruction.Create(OpCodes.Call, i)); |
|||
} |
|||
|
|||
} |
|||
|
|||
|
|||
|
|||
static void PatchMethod(MethodDefinition method) |
|||
{ |
|||
if (method.HasBody) |
|||
{ |
|||
var ilProcessor = method.Body.GetILProcessor(); |
|||
|
|||
var instructions = method.Body.Instructions; |
|||
for (int i = 0; i < instructions.Count; i++) |
|||
{ |
|||
Instruction instruction = instructions[i]; |
|||
|
|||
if (instruction.OpCode == OpCodes.Call && instruction.Operand is MethodReference) |
|||
{ |
|||
var methodDescription = (MethodReference)instruction.Operand; |
|||
|
|||
if (methodDescription.Name.StartsWith("Calli") && methodDescription.DeclaringType.Name == "LocalInterop") |
|||
{ |
|||
var callSite = new CallSite(methodDescription.ReturnType) { CallingConvention = MethodCallingConvention.StdCall }; |
|||
|
|||
if (methodDescription.Name.StartsWith("CalliCdecl")) |
|||
{ |
|||
callSite.CallingConvention = MethodCallingConvention.C; |
|||
} |
|||
else if(methodDescription.Name.StartsWith("CalliThisCall")) |
|||
{ |
|||
callSite.CallingConvention = MethodCallingConvention.ThisCall; |
|||
} |
|||
else if(methodDescription.Name.StartsWith("CalliStdCall")) |
|||
{ |
|||
callSite.CallingConvention = MethodCallingConvention.StdCall; |
|||
} |
|||
else if(methodDescription.Name.StartsWith("CalliFastCall")) |
|||
{ |
|||
callSite.CallingConvention = MethodCallingConvention.FastCall; |
|||
} |
|||
|
|||
// Last parameter is the function ptr, so we don't add it as a parameter for calli
|
|||
// as it is already an implicit parameter for calli
|
|||
for (int j = 0; j < methodDescription.Parameters.Count - 1; j++) |
|||
{ |
|||
var parameterDefinition = methodDescription.Parameters[j]; |
|||
callSite.Parameters.Add(parameterDefinition); |
|||
} |
|||
|
|||
// Create calli Instruction
|
|||
var callIInstruction = ilProcessor.Create(OpCodes.Calli, callSite); |
|||
|
|||
// Replace instruction
|
|||
ilProcessor.Replace(instruction, callIInstruction); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Patches the type.
|
|||
/// </summary>
|
|||
/// <param name="type">The type.</param>
|
|||
static void PatchType(TypeDefinition type, List<TypeDefinition> classToRemoveList) |
|||
{ |
|||
// Patch methods
|
|||
foreach (var method in type.Methods) |
|||
PatchMethod(method); |
|||
|
|||
if (type.Name == "LocalInterop") |
|||
classToRemoveList.Add(type); |
|||
|
|||
// Patch nested types
|
|||
foreach (var typeDefinition in type.NestedTypes) |
|||
PatchType(typeDefinition, classToRemoveList); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> |
|||
</PropertyGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,9 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.MicroCom |
|||
{ |
|||
public interface IMicroComExceptionCallback |
|||
{ |
|||
void RaiseException(Exception e); |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
namespace Avalonia.MicroCom |
|||
{ |
|||
public interface IMicroComShadowContainer |
|||
{ |
|||
MicroComShadow Shadow { get; set; } |
|||
void OnReferencedFromNative(); |
|||
void OnUnreferencedFromNative(); |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.MicroCom |
|||
{ |
|||
public interface IUnknown : IDisposable |
|||
{ |
|||
void AddRef(); |
|||
void Release(); |
|||
int QueryInterface(Guid guid, out IntPtr ppv); |
|||
T QueryInterface<T>() where T : IUnknown; |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.MicroCom |
|||
{ |
|||
unsafe class LocalInterop |
|||
{ |
|||
public static unsafe void CalliStdCallvoid(void* thisObject, void* methodPtr) |
|||
{ |
|||
throw null; |
|||
} |
|||
|
|||
public static unsafe int CalliStdCallint(void* thisObject, Guid* guid, IntPtr* ppv, void* methodPtr) |
|||
{ |
|||
throw null; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,78 @@ |
|||
using System; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace Avalonia.MicroCom |
|||
{ |
|||
public unsafe class MicroComProxyBase : IUnknown |
|||
{ |
|||
private IntPtr _nativePointer; |
|||
private bool _ownsHandle; |
|||
|
|||
public IntPtr NativePointer |
|||
{ |
|||
get |
|||
{ |
|||
if (_nativePointer == IntPtr.Zero) |
|||
throw new ObjectDisposedException(this.GetType().FullName); |
|||
return _nativePointer; |
|||
} |
|||
} |
|||
|
|||
public void*** PPV => (void***)NativePointer; |
|||
|
|||
public MicroComProxyBase(IntPtr nativePointer, bool ownsHandle) |
|||
{ |
|||
_nativePointer = nativePointer; |
|||
_ownsHandle = ownsHandle; |
|||
} |
|||
|
|||
protected virtual int VTableSize => 3; |
|||
|
|||
public void AddRef() |
|||
{ |
|||
LocalInterop.CalliStdCallvoid(PPV, (*PPV)[1]); |
|||
} |
|||
|
|||
public void Release() |
|||
{ |
|||
LocalInterop.CalliStdCallvoid(PPV, (*PPV)[2]); |
|||
} |
|||
|
|||
public int QueryInterface(Guid guid, out IntPtr ppv) |
|||
{ |
|||
IntPtr r = default; |
|||
var rv = LocalInterop.CalliStdCallint(PPV, &guid, &r, (*PPV)[0]); |
|||
ppv = r; |
|||
return rv; |
|||
} |
|||
|
|||
public T QueryInterface<T>() where T : IUnknown |
|||
{ |
|||
var guid = MicroComRuntime.GetGuidFor(typeof(T)); |
|||
var rv = QueryInterface(guid, out var ppv); |
|||
if (rv != 0) |
|||
return (T)MicroComRuntime.CreateProxyFor(typeof(T), ppv, true); |
|||
throw new COMException("QueryInterface failed", rv); |
|||
} |
|||
|
|||
public bool IsDisposed => _nativePointer == IntPtr.Zero; |
|||
|
|||
public void Dispose() |
|||
{ |
|||
if (_ownsHandle) |
|||
Release(); |
|||
_nativePointer = IntPtr.Zero; |
|||
} |
|||
|
|||
public bool OwnsHandle => _ownsHandle; |
|||
|
|||
public void EnsureOwned() |
|||
{ |
|||
if (!_ownsHandle) |
|||
{ |
|||
AddRef(); |
|||
_ownsHandle = true; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,88 @@ |
|||
using System; |
|||
using System.Collections.Concurrent; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace Avalonia.MicroCom |
|||
{ |
|||
public static unsafe class MicroComRuntime |
|||
{ |
|||
private static ConcurrentDictionary<Type, IntPtr> _vtables = new ConcurrentDictionary<Type, IntPtr>(); |
|||
|
|||
private static ConcurrentDictionary<Type, Func<IntPtr, bool, object>> _factories = |
|||
new ConcurrentDictionary<Type, Func<IntPtr, bool, object>>(); |
|||
private static ConcurrentDictionary<Type, Guid> _guids = new ConcurrentDictionary<Type, Guid>(); |
|||
private static ConcurrentDictionary<Guid, Type> _guidsToTypes = new ConcurrentDictionary<Guid, Type>(); |
|||
|
|||
static MicroComRuntime() |
|||
{ |
|||
Register(typeof(IUnknown), new Guid("00000000-0000-0000-C000-000000000046"), |
|||
(ppv, owns) => new MicroComProxyBase(ppv, owns)); |
|||
RegisterVTable(typeof(IUnknown), MicroComVtblBase.Vtable); |
|||
} |
|||
|
|||
public static void RegisterVTable(Type t, IntPtr vtable) |
|||
{ |
|||
_vtables[t] = vtable; |
|||
} |
|||
|
|||
public static void Register(Type t, Guid guid, Func<IntPtr, bool, object> proxyFactory) |
|||
{ |
|||
_factories[t] = proxyFactory; |
|||
_guids[t] = guid; |
|||
_guidsToTypes[guid] = t; |
|||
} |
|||
|
|||
public static Guid GetGuidFor(Type type) => _guids[type]; |
|||
|
|||
public static T CreateProxyFor<T>(void* ppv, bool ownsHandle) => (T)CreateProxyFor(typeof(T), new IntPtr(ppv), ownsHandle); |
|||
|
|||
public static object CreateProxyFor(Type type, IntPtr ppv, bool ownsHandle) => _factories[type](ppv, ownsHandle); |
|||
|
|||
public static void* GetNativePointer<T>(T obj, bool owned = false) where T : IUnknown |
|||
{ |
|||
if (obj is MicroComProxyBase proxy) |
|||
return (void*)proxy.NativePointer; |
|||
if (obj is IMicroComShadowContainer container) |
|||
{ |
|||
container.Shadow ??= new MicroComShadow(container); |
|||
void* ptr = null; |
|||
var res = container.Shadow.GetOrCreateNativePointer(typeof(T), &ptr); |
|||
if (res != 0) |
|||
throw new COMException( |
|||
"Unable to create native callable wrapper for type " + typeof(T) + " for instance of type " + |
|||
obj.GetType(), |
|||
res); |
|||
if (owned) |
|||
container.Shadow.AddRef((Ccw*)ptr); |
|||
} |
|||
throw new ArgumentException("Unable to get a native pointer for " + obj); |
|||
} |
|||
|
|||
public static object GetObjectFromCcw(IntPtr ccw) |
|||
{ |
|||
var ptr = (Ccw*)ccw; |
|||
var shadow = (MicroComShadow)GCHandle.FromIntPtr(ptr->GcShadowHandle).Target; |
|||
return shadow.Target; |
|||
} |
|||
|
|||
public static bool TryGetTypeForGuid(Guid guid, out Type t) => _guidsToTypes.TryGetValue(guid, out t); |
|||
|
|||
public static bool GetVtableFor(Type type, out IntPtr ptr) => _vtables.TryGetValue(type, out ptr); |
|||
|
|||
public static void UnhandledException(object target, Exception e) |
|||
{ |
|||
if (target is IMicroComExceptionCallback cb) |
|||
{ |
|||
try |
|||
{ |
|||
cb.RaiseException(e); |
|||
} |
|||
catch |
|||
{ |
|||
// We've tried
|
|||
} |
|||
} |
|||
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,171 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Runtime.InteropServices; |
|||
using System.Threading; |
|||
|
|||
namespace Avalonia.MicroCom |
|||
{ |
|||
public unsafe class MicroComShadow : IDisposable |
|||
{ |
|||
private readonly object _lock = new object(); |
|||
private readonly Dictionary<Type, IntPtr> _shadows = new Dictionary<Type, IntPtr>(); |
|||
private readonly Dictionary<IntPtr, Type> _backShadows = new Dictionary<IntPtr, Type>(); |
|||
private GCHandle? _handle; |
|||
private volatile int _refCount; |
|||
internal IMicroComShadowContainer Target { get; } |
|||
internal MicroComShadow(IMicroComShadowContainer target) |
|||
{ |
|||
Target = target; |
|||
Target.Shadow = this; |
|||
} |
|||
|
|||
internal int QueryInterface(Ccw* ccw, Guid* guid, void** ppv) |
|||
{ |
|||
if (MicroComRuntime.TryGetTypeForGuid(*guid, out var type)) |
|||
return QueryInterface(type, ppv); |
|||
else |
|||
return unchecked((int)0x80004002u); |
|||
} |
|||
|
|||
internal int QueryInterface(Type type, void** ppv) |
|||
{ |
|||
if (!type.IsInstanceOfType(Target)) |
|||
return unchecked((int)0x80004002u); |
|||
|
|||
var rv = GetOrCreateNativePointer(type, ppv); |
|||
if (rv == 0) |
|||
AddRef((Ccw*)*ppv); |
|||
return rv; |
|||
} |
|||
|
|||
internal int GetOrCreateNativePointer(Type type, void** ppv) |
|||
{ |
|||
if (!MicroComRuntime.GetVtableFor(type, out var vtable)) |
|||
return unchecked((int)0x80004002u); |
|||
lock (_lock) |
|||
{ |
|||
|
|||
if (_shadows.TryGetValue(type, out var shadow)) |
|||
{ |
|||
var targetCcw = (Ccw*)shadow; |
|||
AddRef(targetCcw); |
|||
*ppv = targetCcw; |
|||
return 0; |
|||
} |
|||
else |
|||
{ |
|||
var intPtr = Marshal.AllocHGlobal(Marshal.SizeOf<Ccw>()); |
|||
var targetCcw = (Ccw*)intPtr; |
|||
*targetCcw = default; |
|||
targetCcw->RefCount = 0; |
|||
targetCcw->VTable = vtable; |
|||
if (_handle == null) |
|||
_handle = GCHandle.Alloc(this); |
|||
targetCcw->GcShadowHandle = GCHandle.ToIntPtr(_handle.Value); |
|||
_shadows[type] = intPtr; |
|||
_backShadows[intPtr] = type; |
|||
*ppv = targetCcw; |
|||
|
|||
return 0; |
|||
} |
|||
} |
|||
} |
|||
|
|||
internal int AddRef(Ccw* ccw) |
|||
{ |
|||
if (Interlocked.Increment(ref _refCount) == 1) |
|||
{ |
|||
try |
|||
{ |
|||
Target.OnUnreferencedFromNative(); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
MicroComRuntime.UnhandledException(Target, e); |
|||
} |
|||
} |
|||
|
|||
return Interlocked.Increment(ref ccw->RefCount); |
|||
} |
|||
|
|||
internal int Release(Ccw* ccw) |
|||
{ |
|||
Interlocked.Decrement(ref _refCount); |
|||
var cnt = Interlocked.Decrement(ref ccw->RefCount); |
|||
if (cnt == 0) |
|||
return FreeCcw(ccw); |
|||
|
|||
return cnt; |
|||
} |
|||
|
|||
int FreeCcw(Ccw* ccw) |
|||
{ |
|||
lock (_lock) |
|||
{ |
|||
// Shadow got resurrected by a call to QueryInterface from another thread
|
|||
if (ccw->RefCount != 0) |
|||
return ccw->RefCount; |
|||
|
|||
var intPtr = new IntPtr(ccw); |
|||
var type = _backShadows[intPtr]; |
|||
_backShadows.Remove(intPtr); |
|||
_shadows.Remove(type); |
|||
Marshal.FreeHGlobal(intPtr); |
|||
if (_shadows.Count == 0) |
|||
{ |
|||
_handle?.Free(); |
|||
_handle = null; |
|||
try |
|||
{ |
|||
Target.OnUnreferencedFromNative(); |
|||
} |
|||
catch(Exception e) |
|||
{ |
|||
MicroComRuntime.UnhandledException(Target, e); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
/* |
|||
Needs to be called to support the following scenario: |
|||
1) Object created |
|||
2) Object passed to native code, shadow is created, CCW is created |
|||
3) Native side has never called AddRef |
|||
|
|||
In that case the GC handle to the shadow object is still alive |
|||
*/ |
|||
|
|||
public void Dispose() |
|||
{ |
|||
lock (_lock) |
|||
{ |
|||
List<IntPtr> toRemove = null; |
|||
foreach (var kv in _backShadows) |
|||
{ |
|||
var ccw = (Ccw*)kv.Key; |
|||
if (ccw->RefCount == 0) |
|||
{ |
|||
toRemove ??= new List<IntPtr>(); |
|||
toRemove.Add(kv.Key); |
|||
} |
|||
} |
|||
|
|||
if(toRemove != null) |
|||
foreach (var intPtr in toRemove) |
|||
FreeCcw((Ccw*)intPtr); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
struct Ccw |
|||
{ |
|||
public IntPtr VTable; |
|||
public IntPtr GcShadowHandle; |
|||
public volatile int RefCount; |
|||
public MicroComShadow GetShadow() => (MicroComShadow)GCHandle.FromIntPtr(GcShadowHandle).Target; |
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace Avalonia.MicroCom |
|||
{ |
|||
public unsafe class MicroComVtblBase |
|||
{ |
|||
private List<IntPtr> _methods = new List<IntPtr>(); |
|||
[UnmanagedFunctionPointerAttribute(CallingConvention.ThisCall)] |
|||
private delegate int AddRefDelegate(Ccw* ccw); |
|||
|
|||
[UnmanagedFunctionPointerAttribute(CallingConvention.ThisCall)] |
|||
private delegate int QueryInterfaceDelegate(Ccw* ccw, Guid* guid, void** ppv); |
|||
|
|||
public static IntPtr Vtable { get; } = new MicroComVtblBase().CreateVTable(); |
|||
public MicroComVtblBase() |
|||
{ |
|||
AddMethod((QueryInterfaceDelegate)QueryInterface); |
|||
AddMethod((AddRefDelegate)AddRef); |
|||
} |
|||
|
|||
protected void AddMethod(Delegate d) |
|||
{ |
|||
GCHandle.Alloc(d); |
|||
_methods.Add(Marshal.GetFunctionPointerForDelegate(d)); |
|||
} |
|||
|
|||
protected unsafe IntPtr CreateVTable() |
|||
{ |
|||
var ptr = (IntPtr*)Marshal.AllocHGlobal((IntPtr.Size + 1) * _methods.Count); |
|||
for (var c = 0; c < _methods.Count; c++) |
|||
ptr[c] = _methods[c]; |
|||
return new IntPtr(ptr); |
|||
} |
|||
|
|||
static int QueryInterface(Ccw* ccw, Guid* guid, void** ppv) => ccw->GetShadow().QueryInterface(ccw, guid, ppv); |
|||
static int AddRef(Ccw* ccw) => ccw->GetShadow().AddRef(ccw); |
|||
static int Release(Ccw* ccw) => ccw->GetShadow().Release(ccw); |
|||
} |
|||
} |
|||
@ -0,0 +1,522 @@ |
|||
@clr-namespace Avalonia.Native.MicroCom |
|||
@clr-access internal |
|||
@cpp-preamble @@ |
|||
#include "com.h" |
|||
#include "key.h" |
|||
#include "stddef.h" |
|||
@@ |
|||
|
|||
enum SystemDecorations { |
|||
SystemDecorationsNone = 0, |
|||
SystemDecorationsBorderOnly = 1, |
|||
SystemDecorationsFull = 2, |
|||
} |
|||
|
|||
struct AvnSize |
|||
{ |
|||
double Width, Height; |
|||
} |
|||
|
|||
struct AvnPixelSize |
|||
{ |
|||
int Width, Height; |
|||
} |
|||
|
|||
struct AvnRect |
|||
{ |
|||
double X, Y, Width, Height; |
|||
} |
|||
|
|||
struct AvnVector |
|||
{ |
|||
double X, Y; |
|||
} |
|||
|
|||
struct AvnPoint |
|||
{ |
|||
double X, Y; |
|||
} |
|||
|
|||
struct AvnScreen |
|||
{ |
|||
AvnRect Bounds; |
|||
AvnRect WorkingArea; |
|||
float PixelDensity; |
|||
bool Primary; |
|||
} |
|||
|
|||
enum AvnPixelFormat |
|||
{ |
|||
kAvnRgb565, |
|||
kAvnRgba8888, |
|||
kAvnBgra8888 |
|||
} |
|||
|
|||
struct AvnFramebuffer |
|||
{ |
|||
void* Data; |
|||
int Width; |
|||
int Height; |
|||
int Stride; |
|||
AvnVector Dpi; |
|||
AvnPixelFormat PixelFormat; |
|||
} |
|||
|
|||
struct AvnColor |
|||
{ |
|||
byte Alpha; |
|||
byte Red; |
|||
byte Green; |
|||
byte Blue; |
|||
} |
|||
|
|||
enum AvnRawMouseEventType |
|||
{ |
|||
LeaveWindow, |
|||
LeftButtonDown, |
|||
LeftButtonUp, |
|||
RightButtonDown, |
|||
RightButtonUp, |
|||
MiddleButtonDown, |
|||
MiddleButtonUp, |
|||
XButton1Down, |
|||
XButton1Up, |
|||
XButton2Down, |
|||
XButton2Up, |
|||
Move, |
|||
Wheel, |
|||
NonClientLeftButtonDown, |
|||
TouchBegin, |
|||
TouchUpdate, |
|||
TouchEnd, |
|||
TouchCancel |
|||
} |
|||
|
|||
enum AvnRawKeyEventType |
|||
{ |
|||
KeyDown, |
|||
KeyUp |
|||
} |
|||
|
|||
enum AvnInputModifiers |
|||
{ |
|||
AvnInputModifiersNone = 0, |
|||
Alt = 1, |
|||
Control = 2, |
|||
Shift = 4, |
|||
Windows = 8, |
|||
LeftMouseButton = 16, |
|||
RightMouseButton = 32, |
|||
MiddleMouseButton = 64, |
|||
XButton1MouseButton = 128, |
|||
XButton2MouseButton = 256 |
|||
} |
|||
|
|||
[class-enum] |
|||
enum AvnDragDropEffects |
|||
{ |
|||
None = 0, |
|||
Copy = 1, |
|||
Move = 2, |
|||
Link = 4, |
|||
} |
|||
|
|||
[class-enum] |
|||
enum AvnDragEventType |
|||
{ |
|||
Enter, |
|||
Over, |
|||
Leave, |
|||
Drop |
|||
} |
|||
|
|||
enum AvnWindowState |
|||
{ |
|||
Normal, |
|||
Minimized, |
|||
Maximized, |
|||
FullScreen, |
|||
} |
|||
|
|||
enum AvnStandardCursorType |
|||
{ |
|||
CursorArrow, |
|||
CursorIbeam, |
|||
CursorWait, |
|||
CursorCross, |
|||
CursorUpArrow, |
|||
CursorSizeWestEast, |
|||
CursorSizeNorthSouth, |
|||
CursorSizeAll, |
|||
CursorNo, |
|||
CursorHand, |
|||
CursorAppStarting, |
|||
CursorHelp, |
|||
CursorTopSide, |
|||
CursorBottomSize, |
|||
CursorLeftSide, |
|||
CursorRightSide, |
|||
CursorTopLeftCorner, |
|||
CursorTopRightCorner, |
|||
CursorBottomLeftCorner, |
|||
CursorBottomRightCorner, |
|||
CursorDragMove, |
|||
CursorDragCopy, |
|||
CursorDragLink, |
|||
CursorNone |
|||
} |
|||
|
|||
enum AvnWindowEdge |
|||
{ |
|||
WindowEdgeNorthWest, |
|||
WindowEdgeNorth, |
|||
WindowEdgeNorthEast, |
|||
WindowEdgeWest, |
|||
WindowEdgeEast, |
|||
WindowEdgeSouthWest, |
|||
WindowEdgeSouth, |
|||
WindowEdgeSouthEast |
|||
} |
|||
|
|||
enum AvnMenuItemToggleType |
|||
{ |
|||
None, |
|||
CheckMark, |
|||
Radio |
|||
} |
|||
|
|||
enum AvnExtendClientAreaChromeHints |
|||
{ |
|||
AvnNoChrome = 0, |
|||
AvnSystemChrome = 0x01, |
|||
AvnPreferSystemChrome = 0x02, |
|||
AvnOSXThickTitleBar = 0x08, |
|||
AvnDefaultChrome = AvnSystemChrome, |
|||
} |
|||
|
|||
[uuid(809c652e-7396-11d2-9771-00a0c9b4d50c)] |
|||
interface IAvaloniaNativeFactory : IUnknown |
|||
{ |
|||
HRESULT Initialize(IAvnGCHandleDeallocatorCallback* deallocator); |
|||
IAvnMacOptions* GetMacOptions(); |
|||
HRESULT CreateWindow(IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnWindow** ppv); |
|||
HRESULT CreatePopup(IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnPopup** ppv); |
|||
HRESULT CreatePlatformThreadingInterface(IAvnPlatformThreadingInterface** ppv); |
|||
HRESULT CreateSystemDialogs(IAvnSystemDialogs** ppv); |
|||
HRESULT CreateScreens(IAvnScreens** ppv); |
|||
HRESULT CreateClipboard(IAvnClipboard** ppv); |
|||
HRESULT CreateDndClipboard(IAvnClipboard** ppv); |
|||
HRESULT CreateCursorFactory(IAvnCursorFactory** ppv); |
|||
HRESULT ObtainGlDisplay(IAvnGlDisplay** ppv); |
|||
HRESULT SetAppMenu(IAvnMenu* menu); |
|||
HRESULT CreateMenu(IAvnMenuEvents* cb, IAvnMenu** ppv); |
|||
HRESULT CreateMenuItem(IAvnMenuItem** ppv); |
|||
HRESULT CreateMenuItemSeperator(IAvnMenuItem** ppv); |
|||
} |
|||
|
|||
[uuid(233e094f-9b9f-44a3-9a6e-6948bbdd9fb1)] |
|||
interface IAvnString : IUnknown |
|||
{ |
|||
HRESULT Pointer(void**retOut); |
|||
HRESULT Length(int*ret); |
|||
} |
|||
|
|||
[uuid(e5aca675-02b7-4129-aa79-d6e417210bda)] |
|||
interface IAvnWindowBase : IUnknown |
|||
{ |
|||
HRESULT Show(); |
|||
HRESULT Hide(); |
|||
HRESULT Close(); |
|||
HRESULT Activate(); |
|||
HRESULT GetClientSize(AvnSize*ret); |
|||
HRESULT GetScaling(double*ret); |
|||
HRESULT SetMinMaxSize(AvnSize minSize, AvnSize maxSize); |
|||
HRESULT Resize(double width, double height); |
|||
HRESULT Invalidate(AvnRect rect); |
|||
HRESULT BeginMoveDrag(); |
|||
HRESULT BeginResizeDrag(AvnWindowEdge edge); |
|||
HRESULT GetPosition(AvnPoint*ret); |
|||
HRESULT SetPosition(AvnPoint point); |
|||
HRESULT PointToClient(AvnPoint point, AvnPoint*ret); |
|||
HRESULT PointToScreen(AvnPoint point, AvnPoint*ret); |
|||
HRESULT ThreadSafeSetSwRenderedFrame(AvnFramebuffer* fb, IUnknown* dispose); |
|||
HRESULT SetTopMost(bool value); |
|||
HRESULT SetCursor(IAvnCursor* cursor); |
|||
HRESULT CreateGlRenderTarget(IAvnGlSurfaceRenderTarget** ret); |
|||
HRESULT SetMainMenu(IAvnMenu* menu); |
|||
HRESULT ObtainNSWindowHandle(void** retOut); |
|||
HRESULT ObtainNSWindowHandleRetained(void** retOut); |
|||
HRESULT ObtainNSViewHandle(void** retOut); |
|||
HRESULT ObtainNSViewHandleRetained(void** retOut); |
|||
HRESULT CreateNativeControlHost(IAvnNativeControlHost** retOut); |
|||
HRESULT BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point, |
|||
IAvnClipboard* clipboard, IAvnDndResultCallback* cb, void* sourceHandle); |
|||
HRESULT SetBlurEnabled(bool enable); |
|||
} |
|||
|
|||
[uuid(83e588f3-6981-4e48-9ea0-e1e569f79a91)] |
|||
interface IAvnPopup : IAvnWindowBase |
|||
{ |
|||
|
|||
} |
|||
|
|||
[uuid(cab661de-49d6-4ead-b59c-eac9b2b6c28d)] |
|||
interface IAvnWindow : IAvnWindowBase |
|||
{ |
|||
HRESULT SetEnabled(bool enable); |
|||
HRESULT SetParent(IAvnWindow* parent); |
|||
HRESULT SetCanResize(bool value); |
|||
HRESULT SetDecorations(SystemDecorations value); |
|||
HRESULT SetTitle(void* utf8Title); |
|||
HRESULT SetTitleBarColor(AvnColor color); |
|||
HRESULT SetWindowState(AvnWindowState state); |
|||
HRESULT GetWindowState(AvnWindowState*ret); |
|||
HRESULT TakeFocusFromChildren(); |
|||
HRESULT SetExtendClientArea(bool enable); |
|||
HRESULT SetExtendClientAreaHints(AvnExtendClientAreaChromeHints hints); |
|||
HRESULT GetExtendTitleBarHeight(double*ret); |
|||
HRESULT SetExtendTitleBarHeight(double value); |
|||
} |
|||
|
|||
[uuid(939b6599-40a8-4710-a4c8-5d72d8f174fb)] |
|||
interface IAvnWindowBaseEvents : IUnknown |
|||
{ |
|||
HRESULT Paint(); |
|||
void Closed(); |
|||
void Activated(); |
|||
void Deactivated(); |
|||
void Resized([const] AvnSize* size); |
|||
void PositionChanged(AvnPoint position); |
|||
void RawMouseEvent(AvnRawMouseEventType type, |
|||
uint timeStamp, |
|||
AvnInputModifiers modifiers, |
|||
AvnPoint point, |
|||
AvnVector delta); |
|||
bool RawKeyEvent(AvnRawKeyEventType type, uint timeStamp, AvnInputModifiers modifiers, uint key); |
|||
bool RawTextInputEvent(uint timeStamp, [const] char* text); |
|||
void ScalingChanged(double scaling); |
|||
void RunRenderPriorityJobs(); |
|||
void LostFocus(); |
|||
AvnDragDropEffects DragEvent(AvnDragEventType type, AvnPoint position, |
|||
AvnInputModifiers modifiers, AvnDragDropEffects effects, |
|||
IAvnClipboard* clipboard, void* dataObjectHandle); |
|||
} |
|||
|
|||
[uuid(1ae178ee-1fcc-447f-b6dd-b7bb727f934c)] |
|||
interface IAvnWindowEvents : IAvnWindowBaseEvents |
|||
{ |
|||
/** |
|||
* Closing Event |
|||
* Called when the user presses the OS window close button. |
|||
* return true to allow the close, return false to prevent close. |
|||
*/ |
|||
bool Closing(); |
|||
|
|||
void WindowStateChanged(AvnWindowState state); |
|||
|
|||
void GotInputWhenDisabled(); |
|||
} |
|||
|
|||
[uuid(e34ae0f8-18b4-48a3-b09d-2e6b19a3cf5e)] |
|||
interface IAvnMacOptions : IUnknown |
|||
{ |
|||
HRESULT SetShowInDock(int show); |
|||
HRESULT SetApplicationTitle(void* utf8string); |
|||
} |
|||
|
|||
[uuid(04c1b049-1f43-418a-9159-cae627ec1367)] |
|||
interface IAvnActionCallback : IUnknown |
|||
{ |
|||
void Run(); |
|||
} |
|||
|
|||
[uuid(6df4d2db-0b80-4f59-ad88-0baa5e21eb14)] |
|||
interface IAvnSignaledCallback : IUnknown |
|||
{ |
|||
void Signaled(int priority, bool priorityContainsMeaningfulValue); |
|||
} |
|||
|
|||
[uuid(97330f88-c22b-4a8e-a130-201520091b01)] |
|||
interface IAvnLoopCancellation : IUnknown |
|||
{ |
|||
void Cancel(); |
|||
} |
|||
|
|||
[uuid(fbc06f3d-7860-42df-83fd-53c4b02dd9c3)] |
|||
interface IAvnPlatformThreadingInterface : IUnknown |
|||
{ |
|||
bool GetCurrentThreadIsLoopThread(); |
|||
void SetSignaledCallback(IAvnSignaledCallback* cb); |
|||
IAvnLoopCancellation* CreateLoopCancellation(); |
|||
HRESULT RunLoop(IAvnLoopCancellation* cancel); |
|||
// Can't pass int* to sharpgentools for some reason |
|||
void Signal(int priority); |
|||
IUnknown* StartTimer(int priority, int ms, IAvnActionCallback* callback); |
|||
} |
|||
|
|||
[uuid(6c621a6e-e4c1-4ae3-9749-83eeeffa09b6)] |
|||
interface IAvnSystemDialogEvents : IUnknown |
|||
{ |
|||
void OnCompleted(int numResults, void* ptrFirstResult); |
|||
} |
|||
|
|||
[uuid(4d7a47db-a944-4061-abe7-62cb6aa0ffd5)] |
|||
interface IAvnSystemDialogs : IUnknown |
|||
{ |
|||
void SelectFolderDialog(IAvnWindow* parentWindowHandle, |
|||
IAvnSystemDialogEvents* events, |
|||
[const] char* title, |
|||
[const] char* initialPath); |
|||
|
|||
void OpenFileDialog(IAvnWindow* parentWindowHandle, |
|||
IAvnSystemDialogEvents* events, |
|||
bool allowMultiple, |
|||
[const] char* title, |
|||
[const] char* initialDirectory, |
|||
[const] char* initialFile, |
|||
[const] char* filters); |
|||
|
|||
void SaveFileDialog(IAvnWindow* parentWindowHandle, |
|||
IAvnSystemDialogEvents* events, |
|||
[const] char* title, |
|||
[const] char* initialDirectory, |
|||
[const] char* initialFile, |
|||
[const] char* filters); |
|||
} |
|||
|
|||
[uuid(9a52bc7a-d8c7-4230-8d34-704a0b70a933)] |
|||
interface IAvnScreens : IUnknown |
|||
{ |
|||
HRESULT GetScreenCount(int* ret); |
|||
HRESULT GetScreen(int index, AvnScreen* ret); |
|||
} |
|||
|
|||
[uuid(792b1bd4-76cc-46ea-bfd0-9d642154b1b3)] |
|||
interface IAvnClipboard : IUnknown |
|||
{ |
|||
HRESULT GetText(char* type, IAvnString**ppv); |
|||
HRESULT SetText(char* type, void* utf8Text); |
|||
HRESULT ObtainFormats(IAvnStringArray**ppv); |
|||
HRESULT GetStrings(char* type, IAvnStringArray**ppv); |
|||
HRESULT SetBytes(char* type, void* utf8Text, int len); |
|||
HRESULT GetBytes(char* type, IAvnString**ppv); |
|||
|
|||
HRESULT Clear(); |
|||
} |
|||
|
|||
[uuid(3f998545-f027-4d4d-bd2a-1a80926d984e)] |
|||
interface IAvnCursor : IUnknown |
|||
{ |
|||
} |
|||
|
|||
[uuid(51ecfb12-c427-4757-a2c9-1596bfce53ef)] |
|||
interface IAvnCursorFactory : IUnknown |
|||
{ |
|||
HRESULT GetCursor(AvnStandardCursorType cursorType, IAvnCursor** retOut); |
|||
} |
|||
|
|||
[uuid(60452465-8616-40af-bc00-042e69828ce7)] |
|||
interface IAvnGlDisplay : IUnknown |
|||
{ |
|||
HRESULT CreateContext(IAvnGlContext* share, IAvnGlContext**ppv); |
|||
void LegacyClearCurrentContext(); |
|||
HRESULT WrapContext(void* native, IAvnGlContext**ppv); |
|||
void* GetProcAddress(char* proc); |
|||
} |
|||
|
|||
[uuid(78c5711e-2a98-40d2-bac4-0cc9a49dc4f3)] |
|||
interface IAvnGlContext : IUnknown |
|||
{ |
|||
HRESULT MakeCurrent(IUnknown** ppv); |
|||
HRESULT LegacyMakeCurrent(); |
|||
int GetSampleCount(); |
|||
int GetStencilSize(); |
|||
void* GetNativeHandle(); |
|||
} |
|||
|
|||
[uuid(931062d2-5bc8-4062-8588-83dd8deb99c2)] |
|||
interface IAvnGlSurfaceRenderTarget : IUnknown |
|||
{ |
|||
HRESULT BeginDrawing(IAvnGlSurfaceRenderingSession** ret); |
|||
} |
|||
|
|||
[uuid(e625b406-f04c-484e-946a-4abd2c6015ad)] |
|||
interface IAvnGlSurfaceRenderingSession : IUnknown |
|||
{ |
|||
HRESULT GetPixelSize(AvnPixelSize* ret); |
|||
HRESULT GetScaling(double* ret); |
|||
} |
|||
|
|||
[uuid(a7724dc1-cf6b-4fa8-9d23-228bf2593edc)] |
|||
interface IAvnMenu : IUnknown |
|||
{ |
|||
HRESULT InsertItem(int index, IAvnMenuItem* item); |
|||
HRESULT RemoveItem(IAvnMenuItem* item); |
|||
HRESULT SetTitle(void* utf8String); |
|||
HRESULT Clear(); |
|||
} |
|||
|
|||
[uuid(59e0586d-bd1c-4b85-9882-80d448b0fed9)] |
|||
interface IAvnPredicateCallback : IUnknown |
|||
{ |
|||
bool Evaluate(); |
|||
} |
|||
|
|||
[uuid(f890219a-1720-4cd5-9a26-cd95fccbf53c)] |
|||
interface IAvnMenuItem : IUnknown |
|||
{ |
|||
HRESULT SetSubMenu(IAvnMenu* menu); |
|||
HRESULT SetTitle(void* utf8String); |
|||
HRESULT SetGesture(void* utf8String, AvnInputModifiers modifiers); |
|||
HRESULT SetAction(IAvnPredicateCallback* predicate, IAvnActionCallback* callback); |
|||
HRESULT SetIsChecked(bool isChecked); |
|||
HRESULT SetToggleType(AvnMenuItemToggleType toggleType); |
|||
HRESULT SetIcon(void* data, size_t length); |
|||
} |
|||
|
|||
[uuid(0af7df53-7632-42f4-a650-0992c361b477)] |
|||
interface IAvnMenuEvents : IUnknown |
|||
{ |
|||
/** |
|||
* NeedsUpdate |
|||
*/ |
|||
void NeedsUpdate(); |
|||
} |
|||
|
|||
[uuid(5142bb41-66ab-49e7-bb37-cd079c000f27)] |
|||
interface IAvnStringArray : IUnknown |
|||
{ |
|||
uint GetCount(); |
|||
HRESULT Get(uint index, IAvnString**ppv); |
|||
} |
|||
|
|||
[uuid(a13d2382-3b3a-4d1c-9b27-8f34653d3f01)] |
|||
interface IAvnDndResultCallback : IUnknown |
|||
{ |
|||
void OnDragAndDropComplete(AvnDragDropEffects effecct); |
|||
} |
|||
|
|||
[uuid(f07c608e-52e9-422d-836e-c70f6e9b80f5)] |
|||
interface IAvnGCHandleDeallocatorCallback : IUnknown |
|||
{ |
|||
void FreeGCHandle(void* handle); |
|||
} |
|||
|
|||
[uuid(91c7f677-f26b-4ff3-93cc-cf15aa966ffa)] |
|||
interface IAvnNativeControlHost : IUnknown |
|||
{ |
|||
HRESULT CreateDefaultChild(void* parent, void** retOut); |
|||
IAvnNativeControlHostTopLevelAttachment* CreateAttachment(); |
|||
void DestroyDefaultChild(void* child); |
|||
} |
|||
|
|||
[uuid(14a9e164-1aae-4271-bb78-7b5230999b52)] |
|||
interface IAvnNativeControlHostTopLevelAttachment : IUnknown |
|||
{ |
|||
void* GetParentHandle(); |
|||
HRESULT InitializeWithChildHandle(void* child); |
|||
HRESULT AttachTo(IAvnNativeControlHost* host); |
|||
void ShowInBounds(float x, float y, float width, float height); |
|||
void HideWithSize(float width, float height); |
|||
void ReleaseChild(); |
|||
} |
|||
@ -0,0 +1,102 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
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 class AstEnumNode : List<AstEnumMemberNode> |
|||
{ |
|||
public List<AstAttributeNode> Attributes { get; set; } = new List<AstAttributeNode>(); |
|||
public string Name { get; set; } |
|||
public override string ToString() => Name; |
|||
} |
|||
|
|||
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() => $"{Name} = {Value}"; |
|||
} |
|||
|
|||
public class AstStructNode : List<AstStructMemberNode> |
|||
{ |
|||
public List<AstAttributeNode> Attributes { get; set; } = new List<AstAttributeNode>(); |
|||
public string Name { get; set; } |
|||
public override string ToString() => Name; |
|||
} |
|||
|
|||
public class AstTypeNode |
|||
{ |
|||
public string Name { get; set; } |
|||
public int PointerLevel { get; set; } |
|||
|
|||
public override string ToString() => Name + new string('*', PointerLevel); |
|||
} |
|||
|
|||
public class AstStructMemberNode |
|||
{ |
|||
public string Name { get; set; } |
|||
public AstTypeNode Type { get; set; } |
|||
|
|||
public override string ToString() => $"{Type} {Name}"; |
|||
} |
|||
|
|||
public class AstInterfaceNode : List<AstInterfaceMemberNode> |
|||
{ |
|||
public List<AstAttributeNode> Attributes { get; set; } = new List<AstAttributeNode>(); |
|||
public string Name { get; set; } |
|||
public string Inherits { get; set; } |
|||
|
|||
public override string ToString() |
|||
{ |
|||
if (Inherits == null) |
|||
return Name; |
|||
return $"{Name} : {Inherits}"; |
|||
} |
|||
} |
|||
|
|||
public class AstInterfaceMemberNode : List<AstInterfaceMemberArgumentNode> |
|||
{ |
|||
public string Name { get; set; } |
|||
public AstTypeNode ReturnType { get; set; } |
|||
public List<AstAttributeNode> Attributes { get; set; } = new List<AstAttributeNode>(); |
|||
|
|||
public override string ToString() => $"{ReturnType} {Name} ({string.Join(", ", this)})"; |
|||
} |
|||
|
|||
public class AstInterfaceMemberArgumentNode |
|||
{ |
|||
public string Name { get; set; } |
|||
public AstTypeNode Type { get; set; } |
|||
public List<AstAttributeNode> Attributes { get; set; } = new List<AstAttributeNode>(); |
|||
|
|||
public override string ToString() => $"{Type} {Name}"; |
|||
} |
|||
|
|||
public class AstIdlNode |
|||
{ |
|||
public List<AstAttributeNode> Attributes { get; set; } = new List<AstAttributeNode>(); |
|||
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>(); |
|||
} |
|||
} |
|||
@ -0,0 +1,228 @@ |
|||
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.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 List<AstAttributeNode> ParseGlobalAttributes(ref TokenParser parser) |
|||
{ |
|||
var rv = new List<AstAttributeNode>(); |
|||
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 List<AstAttributeNode> ParseLocalAttributes(ref TokenParser parser) |
|||
{ |
|||
var rv = new List<AstAttributeNode>(); |
|||
if (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)); |
|||
return rv; |
|||
} |
|||
// 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(List<AstAttributeNode> 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++; |
|||
return t; |
|||
} |
|||
|
|||
static AstStructNode ParseStruct(List<AstAttributeNode> 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 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 }); |
|||
} |
|||
|
|||
if (!parsedAtLeastOneMember) |
|||
throw new ParseException("Expected at least one enum member with declared type " + t, ref parser); |
|||
} |
|||
|
|||
return rv; |
|||
} |
|||
|
|||
static AstInterfaceNode ParseInterface(List<AstAttributeNode> 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; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,453 @@ |
|||
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 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(string name, AstTypeNode type) |
|||
{ |
|||
type = new AstTypeNode { Name = ConvertNativeType(type.Name), PointerLevel = type.PointerLevel }; |
|||
|
|||
if (type.PointerLevel == 2) |
|||
{ |
|||
if (type.Name.StartsWith("I")) |
|||
return new InterfaceReturnArg { Name = name, InterfaceType = type.Name, NativeType = "void**" }; |
|||
} |
|||
else if (type.PointerLevel == 1) |
|||
{ |
|||
if (type.Name.StartsWith("I")) |
|||
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
|
|||
var args = member.Select(a => ConvertArg(a.Name, a.Type)).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") |
|||
&& ((member.Last().Type.PointerLevel > 0 |
|||
&& !member.Last().Type.Name |
|||
.StartsWith("I")) |
|||
|| 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 + "]"))); |
|||
|
|||
// 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()); |
|||
|
|||
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); |
|||
|
|||
|
|||
backBodyBlock = Block( |
|||
TryStatement( |
|||
SingletonList(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;") |
|||
)))) |
|||
.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) |
|||
{ |
|||
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.Attributes.FirstOrDefault(x => x.Name == "uuid")?.Value; |
|||
if (guidString == null) |
|||
throw new CodeGenException("Missing GUID for " + iface.Name); |
|||
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)); |
|||
|
|||
var proxyClassName = "__MicroCom" + iface.Name + "Proxy"; |
|||
var proxy = ClassDeclaration(proxyClassName) |
|||
.AddModifiers(Token(SyntaxKind.UnsafeKeyword), Token(_visibility)) |
|||
.WithBaseType(inheritsUnknown ? |
|||
"Avalonia.MicroCom.MicroComProxyBase" : |
|||
("__MicroCom" + iface.Inherits + "Proxy")); |
|||
|
|||
|
|||
// 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(" + |
|||
proxyClassName + "), 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(" + |
|||
proxyClassName + "), 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)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,97 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Linq; |
|||
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 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) |
|||
{ |
|||
if(method.Identifier.Text.Contains("GetScaling")) |
|||
Console.WriteLine(); |
|||
return (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]); |
|||
} |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,89 @@ |
|||
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; |
|||
_extraUsings = _idl.Attributes.Where(u => u.Name == "clr-using").Select(u => u.Value).ToList(); |
|||
_namespace = _idl.Attributes.FirstOrDefault(x => x.Name == "clr-namespace")?.Value; |
|||
if (_namespace == null) |
|||
throw new CodeGenException("Missing clr-namespace attribute"); |
|||
var visibilityString = _idl.Attributes.FirstOrDefault(x => x.Name == "clr-access")?.Value; |
|||
if (visibilityString == null) |
|||
throw new CodeGenException("Missing clr-visibility attribute"); |
|||
if (visibilityString == "internal") |
|||
_visibility = SyntaxKind.InternalKeyword; |
|||
else if (visibilityString == "public") |
|||
_visibility = SyntaxKind.PublicKeyword; |
|||
else |
|||
throw new CodeGenException("Invalid clr-access attribute"); |
|||
} |
|||
|
|||
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 => |
|||
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; |
|||
}))) |
|||
).ToArray()); |
|||
} |
|||
|
|||
NamespaceDeclarationSyntax GenerateStructs(NamespaceDeclarationSyntax ns) |
|||
{ |
|||
return ns.AddMembers(_idl.Structs.Select(e => |
|||
StructDeclaration(e.Name) |
|||
.WithModifiers(TokenList(Token(_visibility))) |
|||
.AddModifiers(Token(SyntaxKind.UnsafeKeyword)) |
|||
.AddAttributeLists(AttributeList(SingletonSeparatedList( |
|||
Attribute(ParseName("System.Runtime.InteropServices.StructLayout"), |
|||
AttributeArgumentList(SingletonSeparatedList( |
|||
AttributeArgument( |
|||
ParseExpression("System.Runtime.InteropServices.LayoutKind.Sequential")))) |
|||
)))) |
|||
.WithMembers(new SyntaxList<MemberDeclarationSyntax>(SeparatedList(e.Select(m => |
|||
DeclareField(m.Type.ToString(), m.Name, SyntaxKind.PublicKeyword))))) |
|||
).ToArray()); |
|||
} |
|||
|
|||
|
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,108 @@ |
|||
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"; |
|||
return name + new string('*', type.PointerLevel); |
|||
} |
|||
|
|||
public static string GenerateCpp(AstIdlNode idl) |
|||
{ |
|||
var sb = new StringBuilder(); |
|||
var preamble = idl.Attributes.FirstOrDefault(x => x.Name == "cpp-preamble")?.Value; |
|||
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.Append(en.Name).Append(" "); |
|||
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.Attributes.FirstOrDefault(x => x.Name == "uuid")?.Value; |
|||
if (guidString == null) |
|||
throw new CodeGenException("Missing uuid for " + i.Name); |
|||
|
|||
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(") : ") |
|||
.AppendLine(i.Inherits ?? "IUnknown") |
|||
.AppendLine("{"); |
|||
|
|||
foreach (var m in i) |
|||
{ |
|||
sb.Append(" ").Append(ConvertType(m.ReturnType)).Append(" ").Append(m.Name).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(" );"); |
|||
} |
|||
} |
|||
|
|||
sb.AppendLine("}"); |
|||
} |
|||
|
|||
return sb.ToString(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,90 @@ |
|||
|
|||
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 string StripPrefix(this string s, string prefix) => string.IsNullOrEmpty(s) |
|||
? s |
|||
: s.StartsWith(prefix) |
|||
? s.Substring(prefix.Length) |
|||
: s; |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<OutputType>Exe</OutputType> |
|||
<TargetFramework>netcoreapp3.1</TargetFramework> |
|||
</PropertyGroup> |
|||
<ItemGroup> |
|||
<PackageReference Include="CommandLineParser" Version="2.8.0" /> |
|||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.7.0" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,27 @@ |
|||
using System; |
|||
|
|||
namespace MicroComGenerator |
|||
{ |
|||
class ParseException : Exception |
|||
{ |
|||
public int Line { get; } |
|||
public int Position { get; } |
|||
|
|||
public ParseException(string message, int line, int position) : base(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) |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
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()); |
|||
|
|||
return 0; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,417 @@ |
|||
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