From bd688d152c98746ed872415f8160cb9f794d239a Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 5 Apr 2023 03:25:12 +0100 Subject: [PATCH] Re-add networking-specific code --- .../CodeGen/BaseCodeGenerator.cs | 26 ++ .../CodeGen/ManagedCodeGenerator.cs | 188 ++++++------- .../CodeGen/NativeCodeGenerator.cs | 134 ++++------ .../Extensions/CXCursorExtensions.cs | 26 -- .../Extensions/ILoggerExtensions.cs | 68 ----- Source/MochaTool.InteropGen/Global.cs | 26 -- .../MochaTool.InteropGen.csproj | 3 - Source/MochaTool.InteropGen/Parser.cs | 195 ++++++++++++++ Source/MochaTool.InteropGen/Parsing/Class.cs | 90 ------- Source/MochaTool.InteropGen/Parsing/IUnit.cs | 36 --- Source/MochaTool.InteropGen/Parsing/Method.cs | 111 -------- Source/MochaTool.InteropGen/Parsing/Parser.cs | 249 ------------------ Source/MochaTool.InteropGen/Parsing/Struct.cs | 74 ------ .../MochaTool.InteropGen/Parsing/Variable.cs | 33 --- Source/MochaTool.InteropGen/Program.cs | 227 +++++++--------- .../Properties/launchSettings.json | 2 +- Source/MochaTool.InteropGen/StopwatchLog.cs | 44 ---- .../MochaTool.InteropGen/ThreadDispatcher.cs | 33 +-- Source/MochaTool.InteropGen/Units/Class.cs | 22 ++ Source/MochaTool.InteropGen/Units/Field.cs | 18 ++ Source/MochaTool.InteropGen/Units/IUnit.cs | 8 + Source/MochaTool.InteropGen/Units/Method.cs | 25 ++ .../MochaTool.InteropGen/Units/Structure.cs | 21 ++ Source/MochaTool.InteropGen/Utils.cs | 100 +++---- .../{Parsing => }/VcxprojParser.cs | 45 ++-- 25 files changed, 615 insertions(+), 1189 deletions(-) create mode 100644 Source/MochaTool.InteropGen/CodeGen/BaseCodeGenerator.cs delete mode 100644 Source/MochaTool.InteropGen/Extensions/CXCursorExtensions.cs delete mode 100644 Source/MochaTool.InteropGen/Extensions/ILoggerExtensions.cs delete mode 100644 Source/MochaTool.InteropGen/Global.cs create mode 100644 Source/MochaTool.InteropGen/Parser.cs delete mode 100644 Source/MochaTool.InteropGen/Parsing/Class.cs delete mode 100644 Source/MochaTool.InteropGen/Parsing/IUnit.cs delete mode 100644 Source/MochaTool.InteropGen/Parsing/Method.cs delete mode 100644 Source/MochaTool.InteropGen/Parsing/Parser.cs delete mode 100644 Source/MochaTool.InteropGen/Parsing/Struct.cs delete mode 100644 Source/MochaTool.InteropGen/Parsing/Variable.cs delete mode 100644 Source/MochaTool.InteropGen/StopwatchLog.cs create mode 100644 Source/MochaTool.InteropGen/Units/Class.cs create mode 100644 Source/MochaTool.InteropGen/Units/Field.cs create mode 100644 Source/MochaTool.InteropGen/Units/IUnit.cs create mode 100644 Source/MochaTool.InteropGen/Units/Method.cs create mode 100644 Source/MochaTool.InteropGen/Units/Structure.cs rename Source/MochaTool.InteropGen/{Parsing => }/VcxprojParser.cs (81%) diff --git a/Source/MochaTool.InteropGen/CodeGen/BaseCodeGenerator.cs b/Source/MochaTool.InteropGen/CodeGen/BaseCodeGenerator.cs new file mode 100644 index 00000000..7587c39c --- /dev/null +++ b/Source/MochaTool.InteropGen/CodeGen/BaseCodeGenerator.cs @@ -0,0 +1,26 @@ +namespace MochaTool.InteropGen; + +internal class BaseCodeGenerator +{ + protected List Units { get; } = new(); + + public BaseCodeGenerator( List units ) + { + Units = units; + } + + protected string GetHeader() + { + return $""" + //------------------------------------------------------------------------------ + // + // This code was generated by a tool. + // InteropGen generated on {DateTime.Now} + // + // Changes to this file may cause incorrect behavior and will be lost if + // the code is regenerated. + // + //------------------------------------------------------------------------------ + """; + } +} diff --git a/Source/MochaTool.InteropGen/CodeGen/ManagedCodeGenerator.cs b/Source/MochaTool.InteropGen/CodeGen/ManagedCodeGenerator.cs index eeb6a576..6cfb0fa1 100644 --- a/Source/MochaTool.InteropGen/CodeGen/ManagedCodeGenerator.cs +++ b/Source/MochaTool.InteropGen/CodeGen/ManagedCodeGenerator.cs @@ -1,103 +1,31 @@ -using MochaTool.InteropGen.Parsing; -using System.CodeDom.Compiler; -using System.Collections.Immutable; +using System.CodeDom.Compiler; -namespace MochaTool.InteropGen.CodeGen; +namespace MochaTool.InteropGen; -/// -/// Contains functionality for generating C# code. -/// -internal static class ManagedCodeGenerator +sealed class ManagedCodeGenerator : BaseCodeGenerator { - /// - /// The namespace that all generated code will be under. - /// - private const string Namespace = "Mocha.Glue"; - - /// - /// An array containing all using declarations for generated code. - /// - private static readonly string[] Usings = new[] + public ManagedCodeGenerator( List units ) : base( units ) { - "System.Runtime.InteropServices", - "System.Runtime.Serialization", - "Mocha.Common" - }; - - /// - /// The header to be used at the top of generated code. - /// - private static string Header => $""" - //------------------------------------------------------------------------------ - // - // This code was generated by a tool. - // InteropGen generated on {DateTime.Now} - // - // Changes to this file may cause incorrect behavior and will be lost if - // the code is regenerated. - // - //------------------------------------------------------------------------------ - """; - - /// - /// Generates and returns C# code for a set of s. - /// - /// An enumerable list of s to generate code for. - /// C# code representing the set of s passed. - internal static string GenerateCode( IEnumerable units ) - { - var (baseTextWriter, writer) = Utils.CreateWriter(); - - // Write header. - writer.WriteLine( Header ); - writer.WriteLine(); - - // Write using statements. - foreach ( var usingStatement in Usings ) - writer.WriteLine( $"using {usingStatement};" ); - - // Write namespace. - writer.WriteLine(); - writer.WriteLine( $"namespace {Namespace};" ); - writer.WriteLine(); - - // Write each unit. - foreach ( var unit in units ) - { - switch ( unit ) - { - case Class c when c.IsNamespace: - GenerateNamespaceCode( writer, c ); - break; - case Class c: - GenerateClassCode( writer, c ); - break; - case Struct s: - GenerateStructCode( writer, s ); - break; - default: - continue; - } + } - writer.WriteLine(); - } + private List GetUsings() + { + return new() { "System.Runtime.InteropServices", "System.Runtime.Serialization", "Mocha.Common" }; + } - return baseTextWriter.ToString(); + private string GetNamespace() + { + return "Mocha.Glue"; } - /// - /// Generates C# code for a class. - /// - /// The writer to append the code to. - /// The class to write code for. - private static void GenerateClassCode( IndentedTextWriter writer, Class c ) + private void GenerateClassCode( ref IndentedTextWriter writer, Class sel ) { // // Gather everything we need into nice lists // List decls = new(); - foreach ( var method in c.Methods ) + foreach ( var method in sel.Methods ) { var returnType = Utils.GetManagedType( method.ReturnType ); var name = method.Name; @@ -128,13 +56,13 @@ private static void GenerateClassCode( IndentedTextWriter writer, Class c ) // any parameters. The return type is the last type argument passed to // the delegate. // - decls.Add( $"private static {delegateSignature} _{name} = ({delegateSignature})Mocha.Common.Global.UnmanagedArgs.__{c.Name}_{name}MethodPtr;" ); + decls.Add( $"private static {delegateSignature} _{name} = ({delegateSignature})Mocha.Common.Global.UnmanagedArgs.__{sel.Name}_{name}MethodPtr;" ); } // // Write shit // - writer.WriteLine( $"public unsafe class {c.Name} : INativeGlue" ); + writer.WriteLine( $"public unsafe class {sel.Name} : INativeGlue" ); writer.WriteLine( "{" ); writer.Indent++; @@ -143,16 +71,18 @@ private static void GenerateClassCode( IndentedTextWriter writer, Class c ) // Decls writer.WriteLine(); foreach ( var decl in decls ) + { writer.WriteLine( decl ); + } writer.WriteLine(); // Ctor - if ( c.Methods.Any( x => x.IsConstructor ) ) + if ( sel.Methods.Any( x => x.IsConstructor ) ) { - var ctor = c.Methods.First( x => x.IsConstructor ); + var ctor = sel.Methods.First( x => x.IsConstructor ); var managedCtorArgs = string.Join( ", ", ctor.Parameters.Select( x => $"{Utils.GetManagedType( x.Type )} {x.Name}" ) ); - writer.WriteLine( $"public {c.Name}( {managedCtorArgs} )" ); + writer.WriteLine( $"public {sel.Name}( {managedCtorArgs} )" ); writer.WriteLine( "{" ); writer.Indent++; @@ -164,7 +94,7 @@ private static void GenerateClassCode( IndentedTextWriter writer, Class c ) } // Methods - foreach ( var method in c.Methods ) + foreach ( var method in sel.Methods ) { writer.WriteLine(); @@ -192,7 +122,7 @@ private static void GenerateClassCode( IndentedTextWriter writer, Class c ) writer.Indent++; // Spin up a MemoryContext instance - writer.WriteLine( $"using var ctx = new MemoryContext( \"{c.Name}.{name}\" );" ); + writer.WriteLine( $"using var ctx = new MemoryContext( \"{sel.Name}.{name}\" );" ); // // Gather function body @@ -201,7 +131,7 @@ private static void GenerateClassCode( IndentedTextWriter writer, Class c ) // We need to pass the instance in if this is not a static method if ( !method.IsStatic ) - paramsAndInstance = paramsAndInstance.Prepend( new Variable( "NativePtr", "IntPtr" ) ).ToImmutableArray(); + paramsAndInstance = paramsAndInstance.Prepend( new Variable( "NativePtr", "IntPtr" ) ).ToList(); // Gather function call arguments. Make sure that we're passing in a pointer for everything var paramNames = paramsAndInstance.Select( x => "ctx.GetPtr( " + x.Name + " )" ); @@ -245,38 +175,30 @@ private static void GenerateClassCode( IndentedTextWriter writer, Class c ) writer.WriteLine( "}" ); } - /// - /// Generates C# code for a struct. - /// - /// The writer to append the code to. - /// The struct to write code for. - private static void GenerateStructCode( IndentedTextWriter writer, Struct s ) + private void GenerateStructCode( ref IndentedTextWriter writer, Structure sel ) { writer.WriteLine( $"[StructLayout( LayoutKind.Sequential )]" ); - writer.WriteLine( $"public struct {s.Name}" ); + writer.WriteLine( $"public struct {sel.Name}" ); writer.WriteLine( "{" ); writer.Indent++; - foreach ( var field in s.Fields ) + foreach ( var field in sel.Fields ) + { writer.WriteLine( $"public {Utils.GetManagedType( field.Type )} {field.Name};" ); + } writer.Indent--; writer.WriteLine( "}" ); } - /// - /// Generates C# code for a namespace. - /// - /// The writer to append the code to. - /// The namespace to write code for. - private static void GenerateNamespaceCode( IndentedTextWriter writer, Class ns ) + private void GenerateNamespaceCode( ref IndentedTextWriter writer, Class sel ) { // // Gather everything we need into nice lists // List decls = new(); - foreach ( var method in ns.Methods ) + foreach ( var method in sel.Methods ) { var returnType = Utils.GetManagedType( method.ReturnType ); var name = method.Name; @@ -298,22 +220,24 @@ private static void GenerateNamespaceCode( IndentedTextWriter writer, Class ns ) // any parameters. The return type is the last type argument passed to // the delegate. // - decls.Add( $"private static {delegateSignature} _{name} = ({delegateSignature})Mocha.Common.Global.UnmanagedArgs.__{ns.Name}_{name}MethodPtr;" ); + decls.Add( $"private static {delegateSignature} _{name} = ({delegateSignature})Mocha.Common.Global.UnmanagedArgs.__{sel.Name}_{name}MethodPtr;" ); } // // Write shit // - writer.WriteLine( $"public static unsafe class {ns.Name}" ); + writer.WriteLine( $"public static unsafe class {sel.Name}" ); writer.WriteLine( "{" ); writer.Indent++; writer.WriteLine(); foreach ( var decl in decls ) + { writer.WriteLine( decl ); + } // Methods - foreach ( var method in ns.Methods ) + foreach ( var method in sel.Methods ) { writer.WriteLine(); @@ -328,9 +252,10 @@ private static void GenerateNamespaceCode( IndentedTextWriter writer, Class ns ) writer.Indent++; // Spin up a MemoryContext instance - writer.WriteLine( $"using var ctx = new MemoryContext( \"{ns.Name}.{name}\" );" ); + writer.WriteLine( $"using var ctx = new MemoryContext( \"{sel.Name}.{name}\" );" ); - var paramNames = method.Parameters.Select( x => "ctx.GetPtr( " + x.Name + " )" ); + var @params = method.Parameters; + var paramNames = @params.Select( x => "ctx.GetPtr( " + x.Name + " )" ); var functionCallArgs = string.Join( ", ", paramNames ); if ( returnsPointer ) @@ -368,4 +293,39 @@ private static void GenerateNamespaceCode( IndentedTextWriter writer, Class ns ) writer.Indent--; writer.WriteLine( "}" ); } + + public string GenerateManagedCode() + { + var (baseTextWriter, writer) = Utils.CreateWriter(); + + writer.WriteLine( GetHeader() ); + writer.WriteLine(); + + foreach ( var usingStatement in GetUsings() ) + writer.WriteLine( $"using {usingStatement};" ); + + writer.WriteLine(); + writer.WriteLine( $"namespace {GetNamespace()};" ); + writer.WriteLine(); + + foreach ( var unit in Units ) + { + if ( unit is Class c ) + { + if ( c.IsNamespace ) + GenerateNamespaceCode( ref writer, c ); + else + GenerateClassCode( ref writer, c ); + } + + if ( unit is Structure s ) + { + GenerateStructCode( ref writer, s ); + } + + writer.WriteLine(); + } + + return baseTextWriter.ToString(); + } } diff --git a/Source/MochaTool.InteropGen/CodeGen/NativeCodeGenerator.cs b/Source/MochaTool.InteropGen/CodeGen/NativeCodeGenerator.cs index 5721ae72..645f06ec 100644 --- a/Source/MochaTool.InteropGen/CodeGen/NativeCodeGenerator.cs +++ b/Source/MochaTool.InteropGen/CodeGen/NativeCodeGenerator.cs @@ -1,40 +1,18 @@ -using MochaTool.InteropGen.Parsing; -using System.CodeDom.Compiler; -using System.Collections.Immutable; +using System.CodeDom.Compiler; -namespace MochaTool.InteropGen.CodeGen; +namespace MochaTool.InteropGen; -/// -/// Contains functionality for generating C++ code. -/// -internal static class NativeCodeGenerator +sealed class NativeCodeGenerator : BaseCodeGenerator { - /// - /// The header to be used at the top of generated code. - /// - private static string Header => $""" - //------------------------------------------------------------------------------ - // - // This code was generated by a tool. - // InteropGen generated on {DateTime.Now} - // - // Changes to this file may cause incorrect behavior and will be lost if - // the code is regenerated. - // - //------------------------------------------------------------------------------ - """; - - /// - /// Generates and returns C++ code for a set of s. - /// - /// The path to the header file that contained the units. - /// An enumerable list of s to generate code for. - /// C++ code representing the set of s passed. - internal static string GenerateCode( string headerPath, IEnumerable units ) + public NativeCodeGenerator( List units ) : base( units ) + { + } + + public string GenerateNativeCode( string headerPath ) { var (baseTextWriter, writer) = Utils.CreateWriter(); - writer.WriteLine( Header ); + writer.WriteLine( GetHeader() ); writer.WriteLine(); writer.WriteLine( "#pragma once" ); @@ -42,15 +20,15 @@ internal static string GenerateCode( string headerPath, IEnumerable units writer.WriteLine(); - foreach ( var unit in units ) + foreach ( var unit in Units ) { - if ( unit is not Class c ) - continue; - - if ( c.IsNamespace ) - GenerateNamespaceCode( writer, c ); - else - GenerateClassCode( writer, c ); + if ( unit is Class c ) + { + if ( c.IsNamespace ) + GenerateNamespaceCode( ref writer, c ); + else + GenerateClassCode( ref writer, c ); + } writer.WriteLine(); } @@ -58,47 +36,34 @@ internal static string GenerateCode( string headerPath, IEnumerable units return baseTextWriter.ToString(); } - /// - /// Generates C++ code for a class. - /// - /// The writer to append the code to. - /// The class to write code for. - private static void GenerateClassCode( IndentedTextWriter writer, Class c ) + private void GenerateNamespaceCode( ref IndentedTextWriter writer, Class c ) { foreach ( var method in c.Methods ) { var args = method.Parameters; - if ( !method.IsStatic ) - args = args.Prepend( new Variable( "instance", $"{c.Name}*" ) ).ToImmutableArray(); - var argStr = string.Join( ", ", args.Select( x => { if ( x.Type == "std::string" ) + { return $"const char* {x.Name}"; + } return $"{x.Type} {x.Name}"; } ) ); var signature = $"extern \"C\" inline {method.ReturnType} __{c.Name}_{method.Name}( {argStr} )"; var body = ""; - var parameters = string.Join( ", ", method.Parameters.Select( x => x.Name ) ); + var @params = string.Join( ", ", method.Parameters.Select( x => x.Name ) ); - if ( method.IsConstructor ) - body += $"return new {c.Name}( {parameters} );"; - else if ( method.IsDestructor ) - body += $"instance->~{c.Name}( {parameters} );"; - else - { - var accessor = method.IsStatic ? $"{c.Name}::" : "instance->"; + var accessor = $"{c.Name}::"; - if ( method.ReturnType == "void" ) - body += $"{accessor}{method.Name}( {parameters} );"; - else if ( method.ReturnType == "std::string" ) - body += $"std::string text = {accessor}{method.Name}( {parameters} );\r\nconst char* cstr = text.c_str();\r\nchar* dup = _strdup(cstr);\r\nreturn dup;"; - else - body += $"return {accessor}{method.Name}( {parameters} );"; - } + if ( method.ReturnType == "void" ) + body += $"{accessor}{method.Name}( {@params} );"; + else if ( method.ReturnType == "std::string" ) + body += $"std::string text = {accessor}{method.Name}( {@params} );\r\nconst char* cstr = text.c_str();\r\nchar* dup = _strdup(cstr);\r\nreturn dup;"; + else + body += $"return {accessor}{method.Name}( {@params} );"; writer.WriteLine( signature ); writer.WriteLine( "{" ); @@ -111,37 +76,48 @@ private static void GenerateClassCode( IndentedTextWriter writer, Class c ) } } - /// - /// Generates C++ code for a namespace. - /// - /// The writer to append the code to. - /// The namespace to write code for. - private static void GenerateNamespaceCode( IndentedTextWriter writer, Class ns ) + private void GenerateClassCode( ref IndentedTextWriter writer, Class c ) { - foreach ( var method in ns.Methods ) + foreach ( var method in c.Methods ) { var args = method.Parameters; + if ( !method.IsStatic ) + args = args.Prepend( new Variable( "instance", $"{c.Name}*" ) ).ToList(); + var argStr = string.Join( ", ", args.Select( x => { if ( x.Type == "std::string" ) + { return $"const char* {x.Name}"; + } return $"{x.Type} {x.Name}"; } ) ); - var signature = $"extern \"C\" inline {method.ReturnType} __{ns.Name}_{method.Name}( {argStr} )"; + var signature = $"extern \"C\" inline {method.ReturnType} __{c.Name}_{method.Name}( {argStr} )"; var body = ""; - var parameters = string.Join( ", ", method.Parameters.Select( x => x.Name ) ); - - var accessor = $"{ns.Name}::"; + var @params = string.Join( ", ", method.Parameters.Select( x => x.Name ) ); - if ( method.ReturnType == "void" ) - body += $"{accessor}{method.Name}( {parameters} );"; - else if ( method.ReturnType == "std::string" ) - body += $"std::string text = {accessor}{method.Name}( {parameters} );\r\nconst char* cstr = text.c_str();\r\nchar* dup = _strdup(cstr);\r\nreturn dup;"; + if ( method.IsConstructor ) + { + body += $"return new {c.Name}( {@params} );"; + } + else if ( method.IsDestructor ) + { + body += $"instance->~{c.Name}( {@params} );"; + } else - body += $"return {accessor}{method.Name}( {parameters} );"; + { + var accessor = method.IsStatic ? $"{c.Name}::" : "instance->"; + + if ( method.ReturnType == "void" ) + body += $"{accessor}{method.Name}( {@params} );"; + else if ( method.ReturnType == "std::string" ) + body += $"std::string text = {accessor}{method.Name}( {@params} );\r\nconst char* cstr = text.c_str();\r\nchar* dup = _strdup(cstr);\r\nreturn dup;"; + else + body += $"return {accessor}{method.Name}( {@params} );"; + } writer.WriteLine( signature ); writer.WriteLine( "{" ); diff --git a/Source/MochaTool.InteropGen/Extensions/CXCursorExtensions.cs b/Source/MochaTool.InteropGen/Extensions/CXCursorExtensions.cs deleted file mode 100644 index 0d385ad2..00000000 --- a/Source/MochaTool.InteropGen/Extensions/CXCursorExtensions.cs +++ /dev/null @@ -1,26 +0,0 @@ -using ClangSharp.Interop; - -namespace MochaTool.InteropGen.Extensions; - -/// -/// Contains extension methods for the . -/// -internal static class CXCursorExtensions -{ - /// - /// Returns whether or not the current item the cursor is over has the "generate_bindings" attribute on it. - /// - /// The cursor to check. - /// Whether or not the current item the cursor is over has the "generate_bindings" attribute on it. - internal static bool HasGenerateBindingsAttribute( this CXCursor cursor ) - { - if ( !cursor.HasAttrs ) - return false; - - var attr = cursor.GetAttr( 0 ); - if ( attr.Spelling.CString != "generate_bindings" ) - return false; - - return true; - } -} diff --git a/Source/MochaTool.InteropGen/Extensions/ILoggerExtensions.cs b/Source/MochaTool.InteropGen/Extensions/ILoggerExtensions.cs deleted file mode 100644 index 0a0056b9..00000000 --- a/Source/MochaTool.InteropGen/Extensions/ILoggerExtensions.cs +++ /dev/null @@ -1,68 +0,0 @@ -using Microsoft.Extensions.Logging; - -namespace MochaTool.InteropGen.Extensions; - -/// -/// Contains extension methods for s. -/// -internal static partial class ILoggerExtensions -{ - /// - /// Logs the first message to the user. - /// - /// The instance to log to. - [LoggerMessage( EventId = 0, - Level = LogLevel.Information, - Message = "Generating C# <--> C++ interop code..." )] - internal static partial void LogIntro( this ILogger logger ); - - /// - /// Logs a timed operation to the user. - /// - /// The instance to log to. - /// The name of the timed operation. - /// The time in seconds that it took to complete the operation. - [LoggerMessage( EventId = 1, - Message = "{name} took {seconds} seconds." )] - internal static partial void ReportTime( this ILogger logger, LogLevel logLevel, string name, double seconds ); - - /// - /// Logs to the user that a header is being processed by the parser. - /// - /// The instance to log to. - /// The absolute path to the header file being processed. - [LoggerMessage( EventId = 2, - Level = LogLevel.Debug, - Message = "Processing header {path}..." )] - internal static partial void ProcessingHeader( this ILogger logger, string path ); - - /// - /// Logs a fatal C++ diagnostic to the user. - /// - /// The instance to log to. - /// The diagnostic to show. - [LoggerMessage( EventId = 3, - Level = LogLevel.Warning, - Message = "{diagnostic}" )] - internal static partial void FatalDiagnostic( this ILogger logger, string diagnostic ); - - /// - /// Logs an error C++ diagnostic to the user. - /// - /// The instance to log to. - /// The diagnostic to show. - [LoggerMessage( EventId = 4, - Level = LogLevel.Warning, - Message = "{diagnostic}" )] - internal static partial void ErrorDiagnostic( this ILogger logger, string diagnostic ); - - /// - /// Logs a warning C++ diagnostic to the user. - /// - /// The instance to log to. - /// The diagnostic to show. - [LoggerMessage( EventId = 5, - Level = LogLevel.Warning, - Message = "{diagnostic}" )] - internal static partial void WarnDiagnostic( this ILogger logger, string diagnostic ); -} diff --git a/Source/MochaTool.InteropGen/Global.cs b/Source/MochaTool.InteropGen/Global.cs deleted file mode 100644 index 9049ac6e..00000000 --- a/Source/MochaTool.InteropGen/Global.cs +++ /dev/null @@ -1,26 +0,0 @@ -global using static MochaTool.InteropGen.Global; -using Microsoft.Extensions.Logging; - -namespace MochaTool.InteropGen; - -/// -/// Contains globally used items in the project. -/// -internal static class Global -{ - /// - /// The instance of to use when logging. - /// - internal static readonly ILogger Log; - - /// - /// Initializes the instance. - /// - static Global() - { - using var factory = LoggerFactory.Create( builder => builder - .AddConsole() - .SetMinimumLevel( LogLevel.Information ) ); - Log = factory.CreateLogger( "InteropGen" ); - } -} diff --git a/Source/MochaTool.InteropGen/MochaTool.InteropGen.csproj b/Source/MochaTool.InteropGen/MochaTool.InteropGen.csproj index 8a1951e0..1ff8a9e9 100644 --- a/Source/MochaTool.InteropGen/MochaTool.InteropGen.csproj +++ b/Source/MochaTool.InteropGen/MochaTool.InteropGen.csproj @@ -15,9 +15,6 @@ - - - diff --git a/Source/MochaTool.InteropGen/Parser.cs b/Source/MochaTool.InteropGen/Parser.cs new file mode 100644 index 00000000..05a6904f --- /dev/null +++ b/Source/MochaTool.InteropGen/Parser.cs @@ -0,0 +1,195 @@ +using ClangSharp.Interop; +namespace MochaTool.InteropGen; + +public static class Parser +{ + /// + /// Cached launch arguments so that we don't have to regenerate them every time + /// + private static string[] s_launchArgs = GetLaunchArgs(); + private static string[] GetLaunchArgs() + { + // Generate includes from vcxproj + var includeDirs = VcxprojParser.ParseIncludes( "../Mocha.Host/Mocha.Host.vcxproj" ); + + var args = new List + { + "-x", + "c++", + "-fparse-all-comments", + "-std=c++20", + "-DVK_NO_PROTOTYPES", + "-DNOMINMAX", + "-DVK_USE_PLATFORM_WIN32_KHR" + }; + + args.AddRange( includeDirs.Select( x => "-I" + x ) ); + + return args.ToArray(); + } + + public unsafe static List GetUnits( string path ) + { + List units = new(); + + using var index = CXIndex.Create(); + using var unit = CXTranslationUnit.Parse( index, path, s_launchArgs, ReadOnlySpan.Empty, CXTranslationUnit_Flags.CXTranslationUnit_None ); + + for ( int i = 0; i < unit.NumDiagnostics; ++i ) + { + var diagnostics = unit.GetDiagnostic( (uint)i ); + Console.WriteLine( $"{diagnostics.Format( CXDiagnostic.DefaultDisplayOptions )}" ); + } + + var cursor = unit.Cursor; + + CXCursorVisitor cursorVisitor = ( CXCursor cursor, CXCursor parent, void* data ) => + { + if ( !cursor.Location.IsFromMainFile ) + return CXChildVisitResult.CXChildVisit_Continue; + + bool HasGenerateBindingsAttribute() + { + if ( !cursor.HasAttrs ) + return false; + + var attr = cursor.GetAttr( 0 ); + if ( attr.Spelling.CString != "generate_bindings" ) + return false; + + return true; + } + + switch ( cursor.Kind ) + { + // + // Struct / class / namespace + // + case CXCursorKind.CXCursor_ClassDecl: + units.Add( new Class( cursor.Spelling.ToString() ) ); + break; + case CXCursorKind.CXCursor_StructDecl: + units.Add( new Structure( cursor.Spelling.ToString() ) ); + break; + case CXCursorKind.CXCursor_Namespace: + units.Add( new Class( cursor.Spelling.ToString() ) + { + IsNamespace = true + } ); + break; + + // + // Methods + // + case CXCursorKind.CXCursor_Constructor: + case CXCursorKind.CXCursor_CXXMethod: + case CXCursorKind.CXCursor_FunctionDecl: + { + if ( !HasGenerateBindingsAttribute() ) + return CXChildVisitResult.CXChildVisit_Continue; + + var oName = cursor.LexicalParent.Spelling.ToString(); + var o = units.FirstOrDefault( x => x.Name == oName ); + var m = new Method( cursor.Spelling.ToString(), cursor.ReturnType.Spelling.ToString() ) + { + IsStatic = cursor.IsStatic + }; + + if ( o == null ) + { + Console.WriteLine( "No unit" ); + break; + } + + CXCursorVisitor methodChildVisitor = ( CXCursor cursor, CXCursor parent, void* data ) => + { + if ( cursor.Kind == CXCursorKind.CXCursor_ParmDecl ) + { + var type = cursor.Type.ToString(); + var name = cursor.Spelling.ToString(); + + var parameter = new Variable( name, type ); + + m.Parameters.Add( parameter ); + } + + return CXChildVisitResult.CXChildVisit_Recurse; + }; + + cursor.VisitChildren( methodChildVisitor, default ); + + if ( cursor.Kind == CXCursorKind.CXCursor_Constructor ) + { + // Constructor specific stuff here + m.ReturnType = $"{o.Name}*"; + m.Name = "Ctor"; + m.IsConstructor = true; + } + + if ( cursor.CXXAccessSpecifier == CX_CXXAccessSpecifier.CX_CXXPublic || cursor.Kind == CXCursorKind.CXCursor_FunctionDecl ) + o.Methods.Add( m ); + + break; + } + + // + // Field + // + case CXCursorKind.CXCursor_FieldDecl: + { + if ( !HasGenerateBindingsAttribute() ) + return CXChildVisitResult.CXChildVisit_Continue; + + var oName = cursor.LexicalParent.Spelling.ToString(); + var s = units.FirstOrDefault( x => x.Name == oName ); + + if ( s == null ) + break; + + s.Fields.Add( new Variable( cursor.Spelling.ToString(), cursor.Type.ToString() ) ); + break; + } + + default: + break; + } + + return CXChildVisitResult.CXChildVisit_Recurse; + }; + + cursor.VisitChildren( cursorVisitor, default ); + + // + // Remove all items with duplicate names + // + for ( int i = 0; i < units.Count; i++ ) + { + var o = units[i]; + o.Methods = o.Methods.GroupBy( x => x.Name ).Select( x => x.First() ).ToList(); + o.Fields = o.Fields.GroupBy( x => x.Name ).Select( x => x.First() ).ToList(); + } + + // + // Remove any units that have no methods or fields + // + units = units.Where( x => x.Methods.Count > 0 || x.Fields.Count > 0 ).ToList(); + + // + // Post-processing + // + //foreach ( var o in units ) + //{ + // // Create a default constructor if one wasn't already defined + // if ( !o.Methods.Any( x => x.IsConstructor ) && o is not Class { IsNamespace: true } ) + // { + // Console.WriteLine( $"Creating default ctor for {o.Name}" ); + // o.Methods.Add( new Method( "Ctor", $"{o.Name}*" ) + // { + // IsConstructor = true + // } ); + // } + //} + + return units; + } +} diff --git a/Source/MochaTool.InteropGen/Parsing/Class.cs b/Source/MochaTool.InteropGen/Parsing/Class.cs deleted file mode 100644 index 0ec9c643..00000000 --- a/Source/MochaTool.InteropGen/Parsing/Class.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System.Collections.Immutable; - -namespace MochaTool.InteropGen.Parsing; - -/// -/// Represents a class or namespace in C++. -/// -internal sealed class Class : IUnit -{ - /// - public string Name { get; } - /// - public bool IsNamespace { get; } - - /// - public ImmutableArray Fields { get; } - /// - public ImmutableArray Methods { get; } - - /// - /// Initializes a new instance of . - /// - /// The name of the class or namespace. - /// Whether or not it is a class or namespace. - /// All of the fields that are contained. - /// All of the methods that are contained. - private Class( string name, bool isNamespace, in ImmutableArray fields, in ImmutableArray methods ) - { - Name = name; - IsNamespace = isNamespace; - - Fields = fields; - Methods = methods; - } - - /// - /// Returns a new instance of the with the fields given. - /// - /// The new fields to place in the instance. - /// A new instance of the with the fields given. - internal Class WithFields( in ImmutableArray fields ) - { - return new Class( Name, IsNamespace, fields, Methods ); - } - - /// - /// Returns a new instance of the with the methods given. - /// - /// The new methods to place in the instance. - /// A new instance of the with the methods given. - internal Class WithMethods( in ImmutableArray methods ) - { - return new Class( Name, IsNamespace, Fields, methods ); - } - - /// - public override string ToString() - { - return Name; - } - - /// - IUnit IUnit.WithFields( in ImmutableArray fields ) => WithFields( fields ); - /// - IUnit IUnit.WithMethods( in ImmutableArray methods ) => WithMethods( methods ); - - /// - /// Returns a new instance of . - /// - /// The name of the class. - /// The fields contained in the class. - /// The methods contained in the class. - /// A new instance of . - internal static Class NewClass( string name, in ImmutableArray fields, in ImmutableArray methods ) - { - return new Class( name, false, fields, methods ); - } - - /// - /// Returns a new instance of as a namespace. - /// - /// The name of the namespace. - /// The fields contained in the namespace. - /// The methods contained in the namespace. - /// A new instance of as a namespace. - internal static Class NewNamespace( string name, in ImmutableArray fields, in ImmutableArray methods ) - { - return new Class( name, true, fields, methods ); - } -} diff --git a/Source/MochaTool.InteropGen/Parsing/IUnit.cs b/Source/MochaTool.InteropGen/Parsing/IUnit.cs deleted file mode 100644 index ad8b68fb..00000000 --- a/Source/MochaTool.InteropGen/Parsing/IUnit.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Collections.Immutable; - -namespace MochaTool.InteropGen.Parsing; - -/// -/// Defines a container for fields and methods defined in C++. -/// -internal interface IUnit -{ - /// - /// The name of the . - /// - string Name { get; } - - /// - /// All of the fields contained in the . - /// - ImmutableArray Fields { get; } - /// - /// All of the methods contained in the . - /// - ImmutableArray Methods { get; } - - /// - /// Returns a new instance of the with the fields given. - /// - /// The new fields to place in the instance. - /// A new instance of the with the fields given. - IUnit WithFields( in ImmutableArray fields ); - /// - /// Returns a new instance of the with the methods given. - /// - /// The new methods to place in the instance. - /// A new instance of the with the methods given. - IUnit WithMethods( in ImmutableArray methods ); -} diff --git a/Source/MochaTool.InteropGen/Parsing/Method.cs b/Source/MochaTool.InteropGen/Parsing/Method.cs deleted file mode 100644 index 8b9540cc..00000000 --- a/Source/MochaTool.InteropGen/Parsing/Method.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System.Collections.Immutable; - -namespace MochaTool.InteropGen.Parsing; - -/// -/// Represents a method in C++. -/// -internal sealed class Method -{ - /// - /// The name of the method. - /// - internal string Name { get; } - /// - /// The literal string containing the return type of the method. - /// - internal string ReturnType { get; } - - /// - /// Whether or not the method is a constructor. - /// - internal bool IsConstructor { get; } = false; - /// - /// Whether or not the method is a destructor. - /// - internal bool IsDestructor { get; } = false; - /// - /// Whether or not the method is static. - /// - internal bool IsStatic { get; } = false; - - /// - /// An array of all the parameters in the method. - /// - internal ImmutableArray Parameters { get; } - - /// - /// Initializes a new instance of . - /// - /// The name of the method. - /// The literal string containing the return type of the method. - /// Whether or not the method is a constructor. - /// Whether or not the method is a destructor. - /// Whether or not the method is static. - /// An array of all the parameters in the method. - private Method( string name, string returnType, bool isConstructor, bool isDestructor, bool isStatic, in ImmutableArray parameters ) - { - Name = name; - ReturnType = returnType; - - IsConstructor = isConstructor; - IsDestructor = isDestructor; - IsStatic = isStatic; - - Parameters = parameters; - } - - /// - /// Returns a new instance of the with the parameters given. - /// - /// The new fields to place in the instance. - /// A new instance of the with the parameters given. - internal Method WithParameters( in ImmutableArray parameters ) - { - return new( Name, ReturnType, IsConstructor, IsDestructor, IsStatic, parameters ); - } - - /// - public override string ToString() - { - var p = string.Join( ", ", Parameters ); - return $"{ReturnType} {Name}( {p} )"; - } - - /// - /// Returns a new instance of as a constructor. - /// - /// The name of the method. - /// The literal string containing the return type of the method. - /// An array of all the parameters in the method. - /// A new instance of as a constructor. - internal static Method NewConstructor( string name, string returnType, in ImmutableArray parameters ) - { - return new( name, returnType, true, false, false, parameters ); - } - - /// - /// Returns a new instance of as a destructor. - /// - /// The name of the method. - /// The literal string containing the return type of the method. - /// An array of all the parameters in the method. - /// A new instance of as a destructor. - internal static Method NewDestructor( string name, string returnType, in ImmutableArray parameters ) - { - return new( name, returnType, false, true, false, parameters ); - } - - /// - /// Returns a new instance of . - /// - /// The name of the method. - /// The literal string containing the return type of the method. - /// Whether or not the method is static. - /// An array of all the parameters in the method. - /// A new instance of . - internal static Method NewMethod( string name, string returnType, bool isStatic, in ImmutableArray parameters ) - { - return new( name, returnType, false, false, isStatic, parameters ); - } -} diff --git a/Source/MochaTool.InteropGen/Parsing/Parser.cs b/Source/MochaTool.InteropGen/Parsing/Parser.cs deleted file mode 100644 index 14810da4..00000000 --- a/Source/MochaTool.InteropGen/Parsing/Parser.cs +++ /dev/null @@ -1,249 +0,0 @@ -using ClangSharp.Interop; -using Microsoft.Extensions.Logging; -using MochaTool.InteropGen.Extensions; -using System.Collections.Immutable; - -namespace MochaTool.InteropGen.Parsing; - -/// -/// Contains all parsing functionality for C++ header files. -/// -internal static class Parser -{ - /// - /// Cached launch arguments so that we don't have to regenerate them every time - /// - private static readonly string[] s_launchArgs = GetLaunchArgs(); - - /// - /// Parses a header file and returns all of the s contained inside. - /// - /// The absolute path to the header file to parse. - /// All of the s contained inside the header file. - internal unsafe static IEnumerable GetUnits( string path ) - { - var units = new List(); - - using var index = CXIndex.Create(); - using var unit = CXTranslationUnit.Parse( index, path, s_launchArgs, ReadOnlySpan.Empty, CXTranslationUnit_Flags.CXTranslationUnit_None ); - - // Only start walking diagnostics if logging is enabled to the minimum level. - if ( Log.IsEnabled( LogLevel.Warning ) ) - { - for ( var i = 0; i < unit.NumDiagnostics; i++ ) - { - var diagnostics = unit.GetDiagnostic( (uint)i ); - switch ( diagnostics.Severity ) - { - case CXDiagnosticSeverity.CXDiagnostic_Fatal: - Log.FatalDiagnostic( diagnostics.Format( CXDiagnostic.DefaultDisplayOptions ).CString ); - break; - case CXDiagnosticSeverity.CXDiagnostic_Error: - Log.ErrorDiagnostic( diagnostics.Format( CXDiagnostic.DefaultDisplayOptions ).CString ); - break; - case CXDiagnosticSeverity.CXDiagnostic_Warning: - Log.WarnDiagnostic( diagnostics.Format( CXDiagnostic.DefaultDisplayOptions ).CString ); - break; - } - } - } - - CXChildVisitResult cursorVisitor( CXCursor cursor, CXCursor parent, void* data ) - { - if ( !cursor.Location.IsFromMainFile ) - return CXChildVisitResult.CXChildVisit_Continue; - - switch ( cursor.Kind ) - { - // - // Struct / class / namespace - // - case CXCursorKind.CXCursor_ClassDecl: - units.Add( Class.NewClass( cursor.Spelling.ToString(), ImmutableArray.Empty, ImmutableArray.Empty ) ); - break; - case CXCursorKind.CXCursor_StructDecl: - units.Add( Struct.NewStructure( cursor.Spelling.ToString(), ImmutableArray.Empty, ImmutableArray.Empty ) ); - break; - case CXCursorKind.CXCursor_Namespace: - units.Add( Class.NewNamespace( cursor.Spelling.ToString(), ImmutableArray.Empty, ImmutableArray.Empty ) ); - break; - - // - // Methods - // - case CXCursorKind.CXCursor_Constructor: - case CXCursorKind.CXCursor_CXXMethod: - case CXCursorKind.CXCursor_FunctionDecl: - return VisitMethod( cursor, units ); - - // - // Field - // - case CXCursorKind.CXCursor_FieldDecl: - return VisitField( cursor, units ); - } - - return CXChildVisitResult.CXChildVisit_Recurse; - } - - unit.Cursor.VisitChildren( cursorVisitor, default ); - - // - // Remove all items with duplicate names - // - for ( int i = 0; i < units.Count; i++ ) - { - var item = units[i]; - item = item.WithFields( item.Fields.GroupBy( x => x.Name ).Select( x => x.First() ).ToImmutableArray() ) - .WithMethods( item.Methods.GroupBy( x => x.Name ).Select( x => x.First() ).ToImmutableArray() ); - - units[i] = item; - } - - // - // Remove any units that have no methods or fields - // - units = units.Where( x => x.Methods.Length > 0 || x.Fields.Length > 0 ).ToList(); - - return units; - } - - /// - /// The visitor method for walking a method declaration. - /// - /// The cursor that is traversing the method. - /// The collection to fetch method owners from. - /// The next action the cursor should take in traversal. - private static unsafe CXChildVisitResult VisitMethod( in CXCursor cursor, ICollection units ) - { - // Early bails. - if ( !cursor.HasGenerateBindingsAttribute() ) - return CXChildVisitResult.CXChildVisit_Continue; - if ( cursor.CXXAccessSpecifier != CX_CXXAccessSpecifier.CX_CXXPublic && cursor.Kind != CXCursorKind.CXCursor_FunctionDecl ) - return CXChildVisitResult.CXChildVisit_Continue; - - // Verify that the method has an owner. - var ownerName = cursor.LexicalParent.Spelling.ToString(); - var owner = units.FirstOrDefault( x => x.Name == ownerName ); - if ( owner is null ) - return CXChildVisitResult.CXChildVisit_Continue; - - string name; - string returnType; - bool isStatic; - bool isConstructor; - bool isDestructor; - - var parametersBuilder = ImmutableArray.CreateBuilder(); - // We're traversing a constructor. - if ( cursor.Kind == CXCursorKind.CXCursor_Constructor ) - { - name = "Ctor"; - returnType = owner.Name + '*'; - isStatic = false; - isConstructor = true; - isDestructor = false; - } - // We're traversing a destructor. - else if ( cursor.Kind == CXCursorKind.CXCursor_Destructor ) - { - name = "DeCtor"; - returnType = '~' + owner.Name; - isStatic = false; - isConstructor = false; - isDestructor = true; - } - // We're traversing a standard method. - else - { - name = cursor.Spelling.ToString(); - returnType = cursor.ReturnType.Spelling.ToString(); - isStatic = cursor.IsStatic; - isConstructor = false; - isDestructor = false; - } - - // Visitor for parameter delcarations. - CXChildVisitResult methodChildVisitor( CXCursor cursor, CXCursor parent, void* data ) - { - if ( cursor.Kind != CXCursorKind.CXCursor_ParmDecl ) - return CXChildVisitResult.CXChildVisit_Continue; - - var name = cursor.Spelling.ToString(); - var type = cursor.Type.ToString(); - - parametersBuilder.Add( new Variable( name, type ) ); - - return CXChildVisitResult.CXChildVisit_Recurse; - } - - cursor.VisitChildren( methodChildVisitor, default ); - - // Construct the method. - Method method; - if ( isConstructor ) - method = Method.NewConstructor( name, returnType, parametersBuilder.ToImmutable() ); - else if ( isDestructor ) - method = Method.NewDestructor( name, returnType, parametersBuilder.ToImmutable() ); - else - method = Method.NewMethod( name, returnType, isStatic, parametersBuilder.ToImmutable() ); - - // Update owner with new method. - var newOwner = owner.WithMethods( owner.Methods.Add( method ) ); - units.Remove( owner ); - units.Add( newOwner ); - - return CXChildVisitResult.CXChildVisit_Continue; - } - - /// - /// The visitor method for walking a field declaration. - /// - /// The cursor that is traversing the method. - /// The collection to fetch method owners from. - /// The next action the cursor should take in traversal. - private static CXChildVisitResult VisitField( in CXCursor cursor, ICollection units ) - { - // Early bail. - if ( !cursor.HasGenerateBindingsAttribute() ) - return CXChildVisitResult.CXChildVisit_Continue; - - // Verify that the field has an owner. - var ownerName = cursor.LexicalParent.Spelling.ToString(); - var owner = units.FirstOrDefault( x => x.Name == ownerName ); - if ( owner is null ) - return CXChildVisitResult.CXChildVisit_Recurse; - - // Update owner with new field. - var newOwner = owner.WithFields( owner.Fields.Add( new Variable( cursor.Spelling.ToString(), cursor.Type.ToString() ) ) ); - units.Remove( owner ); - units.Add( newOwner ); - - return CXChildVisitResult.CXChildVisit_Recurse; - } - - /// - /// Returns a compiled array of launch arguments to pass to the C++ parser. - /// - /// A compiled array of launch arguments to pass to the C++ parser. - private static string[] GetLaunchArgs() - { - // Generate includes from vcxproj - var includeDirs = VcxprojParser.ParseIncludes( "../Mocha.Host/Mocha.Host.vcxproj" ); - - var args = new List - { - "-x", - "c++", - "-fparse-all-comments", - "-std=c++20", - "-DVK_NO_PROTOTYPES", - "-DNOMINMAX", - "-DVK_USE_PLATFORM_WIN32_KHR" - }; - - args.AddRange( includeDirs.Select( x => "-I" + x ) ); - - return args.ToArray(); - } -} diff --git a/Source/MochaTool.InteropGen/Parsing/Struct.cs b/Source/MochaTool.InteropGen/Parsing/Struct.cs deleted file mode 100644 index d48fd6ba..00000000 --- a/Source/MochaTool.InteropGen/Parsing/Struct.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System.Collections.Immutable; - -namespace MochaTool.InteropGen.Parsing; - -/// -/// Represents a struct in C++. -/// -internal sealed class Struct : IUnit -{ - /// - public string Name { get; } - - /// - public ImmutableArray Fields { get; } - /// - public ImmutableArray Methods { get; } - - /// - /// Initializes a new instance of . - /// - /// The name of the struct. - /// The fields contained in the struct. - /// The methods contained in the struct. - private Struct( string name, in ImmutableArray fields, in ImmutableArray methods ) - { - Name = name; - - Fields = fields; - Methods = methods; - } - - /// - /// Returns a new instance of the with the fields given. - /// - /// The new fields to place in the instance. - /// A new instance of the with the fields given. - internal Struct WithFields( in ImmutableArray fields ) - { - return new( Name, fields, Methods ); - } - - /// - /// Returns a new instance of the with the methods given. - /// - /// The new methods to place in the instance. - /// A new instance of the with the methods given. - internal Struct WithMethods( in ImmutableArray methods ) - { - return new( Name, Fields, methods ); - } - - /// - public override string ToString() - { - return Name; - } - - /// - IUnit IUnit.WithFields( in ImmutableArray fields ) => WithFields( fields ); - /// - IUnit IUnit.WithMethods( in ImmutableArray methods ) => WithMethods( methods ); - - /// - /// Returns a new instance of . - /// - /// The name of the struct. - /// The fields contained in the struct. - /// The methods contained in the struct. - /// A new instance of . - internal static Struct NewStructure( string name, in ImmutableArray fields, in ImmutableArray methods ) - { - return new( name, fields, methods ); - } -} diff --git a/Source/MochaTool.InteropGen/Parsing/Variable.cs b/Source/MochaTool.InteropGen/Parsing/Variable.cs deleted file mode 100644 index 60c5e1e8..00000000 --- a/Source/MochaTool.InteropGen/Parsing/Variable.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace MochaTool.InteropGen.Parsing; - -/// -/// Represents a variable in C++. This can be a field, parameter, etc. -/// -internal sealed class Variable -{ - /// - /// The name of the variable. - /// - internal string Name { get; } - /// - /// The literal string containing the type of the variable. - /// - internal string Type { get; } - - /// - /// Initializes a new instance of . - /// - /// The name of the variable. - /// The literal string containing the type of the variable. - internal Variable( string name, string type ) - { - Name = name; - Type = type; - } - - /// - public override string ToString() - { - return $"{Type} {Name}"; - } -} diff --git a/Source/MochaTool.InteropGen/Program.cs b/Source/MochaTool.InteropGen/Program.cs index 6e815d58..d4dce2f7 100644 --- a/Source/MochaTool.InteropGen/Program.cs +++ b/Source/MochaTool.InteropGen/Program.cs @@ -1,110 +1,86 @@ -using Microsoft.Extensions.Logging; -using MochaTool.InteropGen.CodeGen; -using MochaTool.InteropGen.Extensions; -using MochaTool.InteropGen.Parsing; +namespace MochaTool.InteropGen; -namespace MochaTool.InteropGen; - -/// -/// The main entry point to the IntropGen program. -/// public static class Program { - /// - /// Contains all of the parsed units to generate bindings for. - /// - private static readonly List s_units = new(); - /// - /// Contains all of the files that need to be generated. - /// - private static readonly List s_files = new(); - - /// - /// The entry point to the program. - /// - /// The command-line arguments given to the program. - public static void Main( string[] args ) + internal static List s_generatedPaths { get; set; } = new(); + internal static List s_units { get; set; } = new(); + internal static List s_files { get; set; } = new(); + + private static void ProcessHeader( string baseDir, string path ) { - using var _totalTime = new StopwatchLog( "InteropGen", LogLevel.Information ); + Console.WriteLine( $"Processing header {path}..." ); - var baseDir = args[0]; - Log.LogIntro(); + var units = Parser.GetUnits( path ); + var fileName = Path.GetFileNameWithoutExtension( path ); - // - // Prep - // - DeleteExistingFiles( baseDir ); + var managedGenerator = new ManagedCodeGenerator( units ); + var managedCode = managedGenerator.GenerateManagedCode(); + File.WriteAllText( $"{baseDir}Mocha.Common\\Glue\\{fileName}.generated.cs", managedCode ); - using ( var _parseTime = new StopwatchLog( "Parsing" ) ) - Parse( baseDir ); + var nativeGenerator = new NativeCodeGenerator( units ); + var relativePath = Path.GetRelativePath( $"{baseDir}/Mocha.Host/", path ); + var nativeCode = nativeGenerator.GenerateNativeCode( relativePath ); - // - // Expand methods out into list of (method name, method) - // - var methods = s_units.OfType().SelectMany( unit => unit.Methods, ( unit, method ) => (unit.Name, method) ).ToList(); + Console.WriteLine( $"{baseDir}Mocha.Host\\generated\\{fileName}.generated.h" ); + File.WriteAllText( $"{baseDir}Mocha.Host\\generated\\{fileName}.generated.h", nativeCode ); - // - // Write files - // - WriteManagedStruct( baseDir, methods ); - WriteNativeStruct( baseDir, methods ); - WriteNativeIncludes( baseDir ); + s_files.Add( fileName ); + s_units.AddRange( units ); } - /// - /// Deletes and re-creates the generated file directories. - /// - /// The base directory that contains the source projects. - private static void DeleteExistingFiles( string baseDir ) + private static void QueueDirectory( ref List queue, string directory ) { - var destCsDir = $"{baseDir}\\Mocha.Common\\Glue"; - var destHeaderDir = $"{baseDir}\\Mocha.Host\\generated"; + foreach ( var file in Directory.GetFiles( directory ) ) + { + if ( file.EndsWith( ".h" ) && !file.EndsWith( ".generated.h" ) ) + { + var fileContents = File.ReadAllText( file ); - if ( Directory.Exists( destHeaderDir ) ) - Directory.Delete( destHeaderDir, true ); - if ( Directory.Exists( destCsDir ) ) - Directory.Delete( destCsDir, true ); + if ( !fileContents.Contains( "GENERATE_BINDINGS", StringComparison.CurrentCultureIgnoreCase ) ) + continue; // Fast early bail - Directory.CreateDirectory( destHeaderDir ); - Directory.CreateDirectory( destCsDir ); + QueueFile( ref queue, file ); + } + } + + foreach ( var subDirectory in Directory.GetDirectories( directory ) ) + { + QueueDirectory( ref queue, subDirectory ); + } + } + + private static void QueueFile( ref List queue, string path ) + { + queue.Add( path ); } - /// - /// Parses all header files in the Mocha.Host project for interop generation. - /// - /// The base directory that contains the source projects. private static void Parse( string baseDir ) { - // Find and queue all of the header files to parse. - var queue = new List(); - QueueDirectory( queue, baseDir + "\\Mocha.Host" ); + List queue = new(); + QueueDirectory( ref queue, baseDir ); - // Dispatch jobs to parse all files. - var dispatcher = new ThreadDispatcher( async ( files ) => + var dispatcher = new ThreadDispatcher( ( files ) => { foreach ( var path in files ) - await ProcessHeaderAsync( baseDir, path ); + { + ProcessHeader( baseDir, path ); + } }, queue ); // Wait for all threads to finish... while ( !dispatcher.IsComplete ) - Thread.Sleep( 1 ); + Thread.Sleep( 500 ); } - /// - /// Writes the C# unmanaged arguments. - /// - /// The base directory that contains the source projects. - /// An enumerable list of all of the methods to write in the struct. - private static void WriteManagedStruct( string baseDir, IEnumerable<(string Name, Method method)> methods ) + private static void WriteManagedStruct( string baseDir, ref List<(string Name, Method method)> methods ) { var (baseManagedStructWriter, managedStructWriter) = Utils.CreateWriter(); - managedStructWriter.WriteLine( "using System.Runtime.InteropServices;" ); + managedStructWriter.WriteLine( $"using System.Runtime.InteropServices;" ); managedStructWriter.WriteLine(); - managedStructWriter.WriteLine( "[StructLayout( LayoutKind.Sequential )]" ); - managedStructWriter.WriteLine( "public struct UnmanagedArgs" ); - managedStructWriter.WriteLine( '{' ); + managedStructWriter.WriteLine( $"[StructLayout( LayoutKind.Sequential )]" ); + managedStructWriter.WriteLine( $"public struct UnmanagedArgs" ); + managedStructWriter.WriteLine( $"{{" ); managedStructWriter.Indent++; @@ -116,18 +92,13 @@ private static void WriteManagedStruct( string baseDir, IEnumerable<(string Name managedStructWriter.Indent--; - managedStructWriter.WriteLine( '}' ); + managedStructWriter.WriteLine( $"}}" ); managedStructWriter.Dispose(); File.WriteAllText( $"{baseDir}/Mocha.Common/Glue/UnmanagedArgs.cs", baseManagedStructWriter.ToString() ); } - /// - /// Writes the C++ unmanaged arguments. - /// - /// The base directory that contains the source projects. - /// An enumerable list of all of the methods to write in the struct. - private static void WriteNativeStruct( string baseDir, IEnumerable<(string Name, Method method)> methods ) + private static void WriteNativeStruct( string baseDir, ref List<(string Name, Method method)> methods ) { var (baseNativeStructWriter, nativeStructWriter) = Utils.CreateWriter(); @@ -136,7 +107,7 @@ private static void WriteNativeStruct( string baseDir, IEnumerable<(string Name, nativeStructWriter.WriteLine( "#include \"InteropList.generated.h\"" ); nativeStructWriter.WriteLine(); nativeStructWriter.WriteLine( "struct UnmanagedArgs" ); - nativeStructWriter.WriteLine( '{' ); + nativeStructWriter.WriteLine( $"{{" ); nativeStructWriter.Indent++; nativeStructWriter.WriteLine( "void* __Root;" ); @@ -146,11 +117,11 @@ private static void WriteNativeStruct( string baseDir, IEnumerable<(string Name, nativeStructWriter.WriteLine(); nativeStructWriter.Indent--; - nativeStructWriter.WriteLine( "};" ); + nativeStructWriter.WriteLine( $"}};" ); nativeStructWriter.WriteLine(); nativeStructWriter.WriteLine( "inline UnmanagedArgs args" ); - nativeStructWriter.WriteLine( '{' ); + nativeStructWriter.WriteLine( $"{{" ); nativeStructWriter.Indent++; nativeStructWriter.WriteLine( "Root::GetInstance()," ); @@ -160,19 +131,15 @@ private static void WriteNativeStruct( string baseDir, IEnumerable<(string Name, nativeStructWriter.WriteLine(); nativeStructWriter.Indent--; - nativeStructWriter.WriteLine( "};" ); + nativeStructWriter.WriteLine( $"}};" ); nativeStructWriter.WriteLine(); - nativeStructWriter.WriteLine( "#endif // __GENERATED_UNMANAGED_ARGS_H" ); + nativeStructWriter.WriteLine( $"#endif // __GENERATED_UNMANAGED_ARGS_H" ); nativeStructWriter.Dispose(); File.WriteAllText( $"{baseDir}Mocha.Host\\generated\\UnmanagedArgs.generated.h", baseNativeStructWriter.ToString() ); } - /// - /// Writes the C++ includes for the host project. - /// - /// The base directory that contains the source projects. private static void WriteNativeIncludes( string baseDir ) { var (baseNativeListWriter, nativeListWriter) = Utils.CreateWriter(); @@ -193,56 +160,48 @@ private static void WriteNativeIncludes( string baseDir ) File.WriteAllText( $"{baseDir}Mocha.Host\\generated\\InteropList.generated.h", baseNativeListWriter.ToString() ); } - /// - /// Parses a header file and generates its C# and C++ interop code. - /// - /// The base directory that contains the source projects. - /// - /// A task that represents the asynchronous operation. - private static async Task ProcessHeaderAsync( string baseDir, string path ) + private static void DeleteExistingFiles( string baseDir ) { - Log.ProcessingHeader( path ); + var destCsDir = $"{baseDir}\\Mocha.Common\\Glue\\"; + var destHeaderDir = $"{baseDir}\\Mocha.Host\\generated\\"; - // Parse header. - var units = Parser.GetUnits( path ); - - // Generate interop code. - var managedCode = ManagedCodeGenerator.GenerateCode( units ); - var relativePath = Path.GetRelativePath( $"{baseDir}/Mocha.Host/", path ); - var nativeCode = NativeCodeGenerator.GenerateCode( relativePath, units ); - - // Write interop code. - var fileName = Path.GetFileNameWithoutExtension( path ); - var csTask = File.WriteAllTextAsync( $"{baseDir}Mocha.Common\\Glue\\{fileName}.generated.cs", managedCode ); - var nativeTask = File.WriteAllTextAsync( $"{baseDir}Mocha.Host\\generated\\{fileName}.generated.h", nativeCode ); - - // Wait for writing to finish. - await Task.WhenAll( csTask, nativeTask ); + if ( Directory.Exists( destHeaderDir ) ) + Directory.Delete( destHeaderDir, true ); + if ( Directory.Exists( destCsDir ) ) + Directory.Delete( destCsDir, true ); - s_files.Add( fileName ); - s_units.AddRange( units ); + Directory.CreateDirectory( destHeaderDir ); + Directory.CreateDirectory( destCsDir ); } - /// - /// Searches the directory for any header files that should be parsed. - /// - /// The queue collection to append to. - /// The absolute path to the directory to search for files. - private static void QueueDirectory( ICollection queue, string directory ) + public static void Main( string[] args ) { - foreach ( var file in Directory.GetFiles( directory, "*.h" ) ) - { - if ( file.EndsWith( ".generated.h" ) ) - continue; + var baseDir = args[0]; + var start = DateTime.Now; - var fileContents = File.ReadAllText( file ); - if ( !fileContents.Contains( "GENERATE_BINDINGS", StringComparison.CurrentCultureIgnoreCase ) ) - continue; // Fast early bail + Console.WriteLine( "Generating C# <--> C++ interop code..." ); - queue.Add( file ); - } + // + // Prep + // + DeleteExistingFiles( baseDir ); + Parse( baseDir ); - foreach ( var subDirectory in Directory.GetDirectories( directory ) ) - QueueDirectory( queue, subDirectory ); + // + // Expand methods out into list of (method name, method) + // + var methods = s_units.OfType().SelectMany( unit => unit.Methods, ( unit, method ) => (unit.Name, method) ).ToList(); + + // + // Write files + // + WriteManagedStruct( baseDir, ref methods ); + WriteNativeStruct( baseDir, ref methods ); + WriteNativeIncludes( baseDir ); + + // Track time & output total duration + var end = DateTime.Now; + var totalTime = end - start; + Console.WriteLine( $"-- Took {totalTime.TotalSeconds} seconds." ); } } diff --git a/Source/MochaTool.InteropGen/Properties/launchSettings.json b/Source/MochaTool.InteropGen/Properties/launchSettings.json index 32179963..4443d61e 100644 --- a/Source/MochaTool.InteropGen/Properties/launchSettings.json +++ b/Source/MochaTool.InteropGen/Properties/launchSettings.json @@ -3,7 +3,7 @@ "Run InteropGen": { "commandName": "Project", "commandLineArgs": "$(SolutionDir)", - "workingDirectory": "$(ProjectDir)" + "workingDirectory": "F:\\Projects\\mocha-native\\Source\\MochaTool.InteropGen" } } } \ No newline at end of file diff --git a/Source/MochaTool.InteropGen/StopwatchLog.cs b/Source/MochaTool.InteropGen/StopwatchLog.cs deleted file mode 100644 index e9ae7f09..00000000 --- a/Source/MochaTool.InteropGen/StopwatchLog.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Microsoft.Extensions.Logging; -using MochaTool.InteropGen.Extensions; -using System.Diagnostics; - -namespace MochaTool.InteropGen; - -/// -/// Represents a scoped logger for recording time taken to complete an operation. -/// -internal readonly struct StopwatchLog : IDisposable -{ - /// - /// The name of the operation being completed. - /// - private readonly string name = "Unknown"; - /// - /// The level at which to log the operation. - /// - private readonly LogLevel logLevel = LogLevel.Debug; - /// - /// The timestamp at which the operation started. - /// - private readonly long startTimestamp = Stopwatch.GetTimestamp(); - - /// - /// Initializes a new instance of . - /// - /// The name of the operation being completed. - /// The level at which to log the operation. - /// The timestamp at which the operation started. - internal StopwatchLog( string name, LogLevel logLevel = LogLevel.Debug, long? startTimestamp = null ) - { - this.name = name; - this.logLevel = logLevel; - this.startTimestamp = startTimestamp ?? Stopwatch.GetTimestamp(); - } - - /// - public void Dispose() - { - // Log the time taken to complete the operation. - Log.ReportTime( logLevel, name, Stopwatch.GetElapsedTime( startTimestamp ).TotalSeconds ); - } -} diff --git a/Source/MochaTool.InteropGen/ThreadDispatcher.cs b/Source/MochaTool.InteropGen/ThreadDispatcher.cs index 2c46e44f..ee0cfeb7 100644 --- a/Source/MochaTool.InteropGen/ThreadDispatcher.cs +++ b/Source/MochaTool.InteropGen/ThreadDispatcher.cs @@ -1,39 +1,33 @@ namespace MochaTool.InteropGen; -internal class ThreadDispatcher +public class ThreadDispatcher { - internal delegate void ThreadCallback( List threadQueue ); - internal delegate Task AsyncThreadCallback( List threadQueue ); + public delegate void ThreadCallback( List threadQueue ); - internal bool IsComplete => _threadsCompleted >= _threadCount; - - private int _threadCount = (int)Math.Ceiling( Environment.ProcessorCount * 0.75 ); + private int _threadCount = 16; private int _threadsCompleted = 0; + public bool IsComplete => _threadsCompleted == _threadCount; - internal ThreadDispatcher( ThreadCallback threadStart, List queue ) - { - Setup( queue, threadQueue => threadStart( threadQueue ) ); - } - - internal ThreadDispatcher( AsyncThreadCallback threadStart, List queue ) - { - Setup( queue, threadQueue => threadStart( threadQueue ).Wait() ); - } - - private void Setup( List queue, Action> threadStart ) + public ThreadDispatcher( ThreadCallback threadStart, List queue ) { - var batchSize = queue.Count / _threadCount - 1; + var batchSize = queue.Count / _threadCount; if ( batchSize == 0 ) return; // Bail to avoid division by zero + var remainder = queue.Count % _threadCount; + + if ( remainder != 0 ) + batchSize++; + var batched = queue .Select( ( Value, Index ) => new { Value, Index } ) .GroupBy( p => p.Index / batchSize ) .Select( g => g.Select( p => p.Value ).ToList() ) .ToList(); - _threadCount = batched.Count; + if ( batched.Count < _threadCount ) + _threadCount = batched.Count; // Min. 1 per thread for ( int i = 0; i < batched.Count; i++ ) { @@ -41,6 +35,7 @@ private void Setup( List queue, Action> threadStart ) var thread = new Thread( () => { threadStart( threadQueue ); + _threadsCompleted++; } ); diff --git a/Source/MochaTool.InteropGen/Units/Class.cs b/Source/MochaTool.InteropGen/Units/Class.cs new file mode 100644 index 00000000..7e436e65 --- /dev/null +++ b/Source/MochaTool.InteropGen/Units/Class.cs @@ -0,0 +1,22 @@ +namespace MochaTool.InteropGen; + +public struct Class : IUnit +{ + public Class( string name ) : this() + { + Name = name; + + Fields = new(); + Methods = new(); + } + + public string Name { get; set; } + public List Methods { get; set; } + public List Fields { get; set; } + public bool IsNamespace { get; set; } + + public override string ToString() + { + return Name; + } +} diff --git a/Source/MochaTool.InteropGen/Units/Field.cs b/Source/MochaTool.InteropGen/Units/Field.cs new file mode 100644 index 00000000..e17a2781 --- /dev/null +++ b/Source/MochaTool.InteropGen/Units/Field.cs @@ -0,0 +1,18 @@ +namespace MochaTool.InteropGen; + +public struct Variable +{ + public Variable( string name, string type ) : this() + { + Name = name; + Type = type; + } + + public string Name { get; set; } + public string Type { get; set; } + + public override string ToString() + { + return $"{Type} {Name}"; + } +} diff --git a/Source/MochaTool.InteropGen/Units/IUnit.cs b/Source/MochaTool.InteropGen/Units/IUnit.cs new file mode 100644 index 00000000..71f88a6c --- /dev/null +++ b/Source/MochaTool.InteropGen/Units/IUnit.cs @@ -0,0 +1,8 @@ +namespace MochaTool.InteropGen; + +public interface IUnit +{ + public string Name { get; set; } + public List Fields { get; set; } + public List Methods { get; set; } +} diff --git a/Source/MochaTool.InteropGen/Units/Method.cs b/Source/MochaTool.InteropGen/Units/Method.cs new file mode 100644 index 00000000..92ffe8b9 --- /dev/null +++ b/Source/MochaTool.InteropGen/Units/Method.cs @@ -0,0 +1,25 @@ +namespace MochaTool.InteropGen; + +public struct Method +{ + public Method( string name, string returnType ) + { + Name = name; + ReturnType = returnType; + Parameters = new(); + } + + public bool IsConstructor { get; set; } = false; + public bool IsDestructor { get; set; } = false; + public bool IsStatic { get; set; } = false; + + public string Name { get; set; } + public string ReturnType { get; set; } + public List Parameters { get; set; } + + public override string ToString() + { + var p = string.Join( ", ", Parameters ); + return $"{ReturnType} {Name}( {p} )"; + } +} diff --git a/Source/MochaTool.InteropGen/Units/Structure.cs b/Source/MochaTool.InteropGen/Units/Structure.cs new file mode 100644 index 00000000..4e2f150f --- /dev/null +++ b/Source/MochaTool.InteropGen/Units/Structure.cs @@ -0,0 +1,21 @@ +namespace MochaTool.InteropGen; + +public struct Structure : IUnit +{ + public Structure( string name ) : this() + { + Name = name; + + Fields = new(); + Methods = new(); + } + + public string Name { get; set; } + public List Methods { get; set; } + public List Fields { get; set; } + + public override string ToString() + { + return Name; + } +} diff --git a/Source/MochaTool.InteropGen/Utils.cs b/Source/MochaTool.InteropGen/Utils.cs index 3bdb93b8..77f528d1 100644 --- a/Source/MochaTool.InteropGen/Utils.cs +++ b/Source/MochaTool.InteropGen/Utils.cs @@ -1,64 +1,16 @@ -using MochaTool.InteropGen.Extensions; -using System.CodeDom.Compiler; +using System.CodeDom.Compiler; namespace MochaTool.InteropGen; -/// -/// Contains a number of utility methods. -/// -internal static class Utils +static class Utils { - /// - /// Used as a lookup table for mapping native types to managed ones. - /// - private static readonly Dictionary s_lookupTable = new() - { - // Native type Managed type - //------------------------------- - { "void", "void" }, - { "uint32_t", "uint" }, - { "int32_t", "int" }, - { "size_t", "uint" }, - - { "char**", "ref string" }, - { "char **", "ref string" }, - { "char*", "string" }, - { "char *", "string" }, - { "void*", "IntPtr" }, - { "void *", "IntPtr" }, - - // STL - { "std::string", "/* UNSUPPORTED */ string" }, - - // GLM - { "glm::vec2", "Vector2" }, - { "glm::vec3", "Vector3" }, - { "glm::mat4", "Matrix4x4" }, - { "glm::quat", "Rotation" }, - - // Custom - { "Quaternion", "Rotation" }, - { "InteropStruct", "IInteropArray" }, - { "Handle", "uint" } - }; - - /// - /// Returns whether or not the string represents a pointer. - /// - /// The native type to check. - /// Whether or not the string represents a pointer. - internal static bool IsPointer( string nativeType ) + public static bool IsPointer( string nativeType ) { var managedType = GetManagedType( nativeType ); return nativeType.Trim().EndsWith( "*" ) && managedType != "string" && managedType != "IntPtr"; } - /// - /// Returns the C# version of a native type. - /// - /// The native type to check. - /// The C# verison of a native type. - internal static string GetManagedType( string nativeType ) + public static string GetManagedType( string nativeType ) { // Trim whitespace from beginning / end (if it exists) nativeType = nativeType.Trim(); @@ -67,22 +19,54 @@ internal static string GetManagedType( string nativeType ) if ( nativeType.StartsWith( "const" ) ) nativeType = nativeType[5..].Trim(); + // Create a dictionary to hold the mapping between native and managed types + var lookupTable = new Dictionary() + { + // Native type Managed type + //------------------------------- + { "void", "void" }, + { "uint32_t", "uint" }, + { "int32_t", "int" }, + { "size_t", "uint" }, + + { "char**", "ref string" }, + { "char **", "ref string" }, + { "char*", "string" }, + { "char *", "string" }, + { "void*", "IntPtr" }, + { "void *", "IntPtr" }, + + // STL + { "std::string", "/* UNSUPPORTED */ string" }, + + // GLM + { "glm::vec2", "Vector2" }, + { "glm::vec3", "Vector3" }, + { "glm::mat4", "Matrix4x4" }, + { "glm::quat", "Rotation" }, + + // Custom + { "Quaternion", "Rotation" }, + { "InteropStruct", "IInteropArray" }, + { "Handle", "uint" } + }; + // Check if the native type is a reference if ( nativeType.EndsWith( "&" ) ) return GetManagedType( nativeType[0..^1] ); // Check if the native type is in the lookup table - if ( s_lookupTable.TryGetValue( nativeType, out var value ) ) + if ( lookupTable.ContainsKey( nativeType ) ) { // Bonus: Emit a compiler warning if the native type is std::string if ( nativeType == "std::string" ) { // There's a better API that does this but I can't remember what it is // TODO: Show position of the warning (line number, file name) - Log.WarnDiagnostic( "warning IG0001: std::string is not supported in managed code. Use a C string instead." ); + Console.WriteLine( "warning IG0001: std::string is not supported in managed code. Use a C string instead." ); } - return value; + return lookupTable[nativeType]; } // Check if the native type is a pointer @@ -93,11 +77,7 @@ internal static string GetManagedType( string nativeType ) return nativeType; } - /// - /// Creates and returns the text writer for writing formatted files. - /// - /// The created text writer. - internal static (StringWriter StringWriter, IndentedTextWriter TextWriter) CreateWriter() + public static (StringWriter StringWriter, IndentedTextWriter TextWriter) CreateWriter() { var baseTextWriter = new StringWriter(); diff --git a/Source/MochaTool.InteropGen/Parsing/VcxprojParser.cs b/Source/MochaTool.InteropGen/VcxprojParser.cs similarity index 81% rename from Source/MochaTool.InteropGen/Parsing/VcxprojParser.cs rename to Source/MochaTool.InteropGen/VcxprojParser.cs index d7317fca..4ddaef8f 100644 --- a/Source/MochaTool.InteropGen/Parsing/VcxprojParser.cs +++ b/Source/MochaTool.InteropGen/VcxprojParser.cs @@ -1,10 +1,7 @@ using System.Xml; -namespace MochaTool.InteropGen.Parsing; +namespace MochaTool.InteropGen; -/// -/// Contains functionality for parsing vcxproj files. -/// internal static class VcxprojParser { // Note that these paths only work for the windows x64 platforms right now. @@ -14,6 +11,19 @@ internal static class VcxprojParser private const string ExternalIncludePath = "/rs:Project/rs:PropertyGroup[@Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\"]/rs:ExternalIncludePath"; private const string IncludePath = "/rs:Project/rs:PropertyGroup[@Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\"]/rs:IncludePath"; + private static string GetNodeContents( XmlNode root, string xpath, XmlNamespaceManager namespaceManager ) + { + var nodeList = root.SelectNodes( xpath, namespaceManager ); + if ( nodeList?.Count == 0 || nodeList?[0] == null ) + throw new Exception( "Couldn't find IncludePath!" ); + +#pragma warning disable CS8602 // Dereference of a possibly null reference. + var includeStr = nodeList[0].InnerText; +#pragma warning restore CS8602 // Dereference of a possibly null reference. + + return includeStr; + } + /// /// Parse the include list from a vcxproj file. /// @@ -21,20 +31,20 @@ internal static class VcxprojParser /// This currently only supports x64-windows, so any different includes for other platforms /// will not be reflected here. /// - internal static List ParseIncludes( string path ) + public static List ParseIncludes( string path ) { - var doc = new XmlDocument(); + XmlDocument doc = new XmlDocument(); doc.Load( path ); - var namespaceManager = new XmlNamespaceManager( doc.NameTable ); + XmlNamespaceManager namespaceManager = new XmlNamespaceManager( doc.NameTable ); namespaceManager.AddNamespace( "rs", "http://schemas.microsoft.com/developer/msbuild/2003" ); - if ( doc.DocumentElement is null ) + if ( doc.DocumentElement == null ) throw new Exception( "Failed to parse root node!" ); - var root = doc.DocumentElement; + XmlNode root = doc.DocumentElement; - var includes = new List(); + List includes = new(); // Select Project -> PropertyGroup -> ExternalIncludePath { @@ -60,7 +70,7 @@ internal static List ParseIncludes( string path ) { "ExternalIncludePath", "" } }; - var parsedIncludes = new List(); + List parsedIncludes = new(); // Simple find-and-replace for macros and environment variables foreach ( var include in includes ) @@ -68,22 +78,13 @@ internal static List ParseIncludes( string path ) var processedInclude = include; foreach ( var environmentVariable in environmentVariables ) + { processedInclude = processedInclude.Replace( $"$({environmentVariable.Key})", environmentVariable.Value ); + } parsedIncludes.Add( processedInclude ); } return parsedIncludes; } - - private static string GetNodeContents( XmlNode root, string xpath, XmlNamespaceManager namespaceManager ) - { - var nodeList = root.SelectNodes( xpath, namespaceManager ); - if ( nodeList?.Count == 0 || nodeList?[0] is null ) - throw new Exception( "Couldn't find IncludePath!" ); - - var includeStr = nodeList[0]!.InnerText; - - return includeStr; - } }