From 2bedfd589896013fe92b85fb31a8ce89783f2c99 Mon Sep 17 00:00:00 2001 From: Thraka Date: Wed, 23 Nov 2022 15:28:59 -0800 Subject: [PATCH] Add new command line system; expose internals --- src/CodeGenerator/CodeGenerator.csproj | 1 + src/CodeGenerator/ImguiDefinitions.cs | 40 ++++-- src/CodeGenerator/Program.cs | 178 +++++++++++++++++-------- src/CodeGenerator/TypeInfo.cs | 12 ++ 4 files changed, 163 insertions(+), 68 deletions(-) diff --git a/src/CodeGenerator/CodeGenerator.csproj b/src/CodeGenerator/CodeGenerator.csproj index 6c2760d..c15d0c0 100644 --- a/src/CodeGenerator/CodeGenerator.csproj +++ b/src/CodeGenerator/CodeGenerator.csproj @@ -25,5 +25,6 @@ + diff --git a/src/CodeGenerator/ImguiDefinitions.cs b/src/CodeGenerator/ImguiDefinitions.cs index 92e7d74..c9cc936 100644 --- a/src/CodeGenerator/ImguiDefinitions.cs +++ b/src/CodeGenerator/ImguiDefinitions.cs @@ -21,7 +21,7 @@ namespace CodeGenerator if (v == null) return 0; return v.ToObject(); } - public void LoadFrom(string directory) + public void LoadFrom(string directory, bool excludeInternals = true) { JObject typesJson; @@ -65,23 +65,27 @@ namespace CodeGenerator { JProperty jp = (JProperty)jt; string name = jp.Name; - if (typeLocations?[jp.Name]?.Value().Contains("internal") ?? false) { + bool isInternal = typeLocations?[jp.Name]?.Value().Contains("internal") ?? false; + + if (excludeInternals && isInternal) return null; - } + EnumMember[] elements = jp.Values().Select(v => { return new EnumMember(v["name"].ToString(), v["calc_value"].ToString()); }).ToArray(); - return new EnumDefinition(name, elements); + return new EnumDefinition(name, elements, isInternal); }).Where(x => x != null).ToArray(); Types = typesJson["structs"].Select(jt => { JProperty jp = (JProperty)jt; string name = jp.Name; - if (typeLocations?[jp.Name]?.Value().Contains("internal") ?? false) { + bool isInternal = typeLocations?[jp.Name]?.Value().Contains("internal") ?? false; + + if (excludeInternals && isInternal) return null; - } + TypeReference[] fields = jp.Values().Select(v => { if (v["type"].ToString().Contains("static")) { return null; } @@ -94,7 +98,7 @@ namespace CodeGenerator v["template_type"]?.ToString(), Enums); }).Where(tr => tr != null).ToArray(); - return new TypeDefinition(name, fields); + return new TypeDefinition(name, fields, isInternal); }).Where(x => x != null).ToArray(); Functions = functionsJson.Children().Select(jt => @@ -120,7 +124,9 @@ namespace CodeGenerator } } if (friendlyName == null) { return null; } - if (val["location"]?.ToString().Contains("internal") ?? false) return null; + bool isInternal = val["location"]?.ToString().Contains("internal") ?? false; + if (excludeInternals && isInternal) + return null; string exportedName = ov_cimguiname; if (exportedName == null) @@ -184,7 +190,8 @@ namespace CodeGenerator structName, comment, isConstructor, - isDestructor); + isDestructor, + isInternal); }).Where(od => od != null).ToArray(); if(overloads.Length == 0) return null; return new FunctionDefinition(name, overloads, Enums); @@ -231,8 +238,9 @@ namespace CodeGenerator public string Name { get; } public string FriendlyName { get; } public EnumMember[] Members { get; } + public bool IsInternal { get; } - public EnumDefinition(string name, EnumMember[] elements) + public EnumDefinition(string name, EnumMember[] elements, bool isInternal) { Name = name; if (Name.EndsWith('_')) @@ -250,6 +258,7 @@ namespace CodeGenerator { _sanitizedNames.Add(el.Name, SanitizeMemberName(el.Name)); } + IsInternal = isInternal; } public string SanitizeNames(string text) @@ -302,11 +311,13 @@ namespace CodeGenerator { public string Name { get; } public TypeReference[] Fields { get; } + public bool IsInternal { get; } - public TypeDefinition(string name, TypeReference[] fields) + public TypeDefinition(string name, TypeReference[] fields, bool isInternal) { Name = name; Fields = fields; + IsInternal = isInternal; } } @@ -496,6 +507,7 @@ namespace CodeGenerator public string Comment { get; } public bool IsConstructor { get; } public bool IsDestructor { get; } + public bool IsInternal { get; } public OverloadDefinition( string exportedName, @@ -506,7 +518,8 @@ namespace CodeGenerator string structName, string comment, bool isConstructor, - bool isDestructor) + bool isDestructor, + bool isInternal) { ExportedName = exportedName; FriendlyName = friendlyName; @@ -518,11 +531,12 @@ namespace CodeGenerator Comment = comment; IsConstructor = isConstructor; IsDestructor = isDestructor; + IsInternal = isInternal; } public OverloadDefinition WithParameters(TypeReference[] parameters) { - return new OverloadDefinition(ExportedName, FriendlyName, parameters, DefaultValues, ReturnType, StructName, Comment, IsConstructor, IsDestructor); + return new OverloadDefinition(ExportedName, FriendlyName, parameters, DefaultValues, ReturnType, StructName, Comment, IsConstructor, IsDestructor, IsInternal); } } } diff --git a/src/CodeGenerator/Program.cs b/src/CodeGenerator/Program.cs index d85f7c2..fb39deb 100644 --- a/src/CodeGenerator/Program.cs +++ b/src/CodeGenerator/Program.cs @@ -8,37 +8,85 @@ using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; +using System.CommandLine; +using System.Linq.Expressions; +using System.Runtime.CompilerServices; namespace CodeGenerator { internal static class Program { - static void Main(string[] args) + private const string InternalNamespace = ".Internal"; + + static async Task Main(string[] args) { - string outputPath; - if (args.Length > 0) - { - outputPath = args[0]; - } - else - { - outputPath = AppContext.BaseDirectory; - } + // internal vars for command line results used by the rest of the program. + bool runApp = false; + string outputPath = string.Empty; + string libraryName = string.Empty; + bool useInternals = false; - if (!Directory.Exists(outputPath)) - { - Directory.CreateDirectory(outputPath); - } + #region Command line handler + var optionOutputPath = new Option( + aliases: new[] { "--outputDir", "-o" }, + description: "The directory to place generated code files.", + parseArgument: result => + { + if (result.Tokens.Count == 0) + return new DirectoryInfo(AppContext.BaseDirectory); - string libraryName; - if (args.Length > 1) - { - libraryName = args[1]; - } - else + string value = result.Tokens.Single().Value; + + try { return Directory.CreateDirectory(value); } + catch (Exception) { result.ErrorMessage = $"Unable to create directory: {value}"; return null; } + }, + isDefault: true); + + var optionLibraryname = new Option( + aliases: new[] { "--library", "-l" }, + description: "The library to read parse.", + getDefaultValue: () => "cimgui") + .FromAmong("cimgui", "cimplot", "cimnodes", "cimguizmo"); + + var optionInternal = new Option( + name: "--internal", + description: "When set to true, includes the internal header file.", + parseArgument: result => + { + // Using parse with isDefault: false, instead of the normal validation, allows us to use "--internal" without specifying true to mean true. + if (result.Tokens.Count == 0) + return true; + + if (bool.TryParse(result.Tokens.Single().Value, out var value)) + return value; + + result.ErrorMessage = "Invalid option for --internal. Value must be true or false."; + return false; // ignored because of error message. + }, + isDefault: false); + + var rootCommand = new RootCommand("Generates code for the ImGui.NET libraries based on the cimgui definition files."); + + rootCommand.AddOption(optionInternal); + rootCommand.AddOption(optionOutputPath); + rootCommand.AddOption(optionLibraryname); + + rootCommand.SetHandler((outputPathValue, libNameValue, useInternalValue) => { - libraryName = "cimgui"; - } + outputPath = outputPathValue.FullName; + libraryName = libNameValue; + useInternals = useInternalValue; + + runApp = true; + + }, optionOutputPath, optionLibraryname, optionInternal); + + var commandResult = await rootCommand.InvokeAsync(args); + + if (!runApp) + return commandResult; + + #endregion string projectNamespace = libraryName switch { @@ -78,7 +126,7 @@ namespace CodeGenerator string definitionsPath = Path.Combine(AppContext.BaseDirectory, "definitions", libraryName); var defs = new ImguiDefinitions(); - defs.LoadFrom(definitionsPath); + defs.LoadFrom(definitionsPath, !useInternals); Console.WriteLine($"Outputting generated code files to {outputPath}."); @@ -118,7 +166,7 @@ namespace CodeGenerator writer.Using("ImGuiNET"); } writer.WriteLine(string.Empty); - writer.PushBlock($"namespace {projectNamespace}"); + writer.PushBlock($"namespace {projectNamespace}{(td.IsInternal ? InternalNamespace : string.Empty)}"); writer.PushBlock($"public unsafe partial struct {td.Name}"); foreach (TypeReference field in td.Fields) @@ -284,6 +332,10 @@ namespace CodeGenerator { writer.Using("ImGuiNET"); } + if (useInternals) + { + writer.Using($"{projectNamespace}{InternalNamespace}"); + } writer.WriteLine(string.Empty); writer.PushBlock($"namespace {projectNamespace}"); writer.PushBlock($"public static unsafe partial class {classPrefix}Native"); @@ -347,6 +399,7 @@ namespace CodeGenerator writer.PopBlock(); } + // Root ImGui* class items - Noninternal using (CSharpCodeWriter writer = new CSharpCodeWriter(Path.Combine(outputPath, $"{classPrefix}.gen.cs"))) { writer.Using("System"); @@ -360,52 +413,65 @@ namespace CodeGenerator writer.WriteLine(string.Empty); writer.PushBlock($"namespace {projectNamespace}"); writer.PushBlock($"public static unsafe partial class {classPrefix}"); - foreach (FunctionDefinition fd in defs.Functions) + EmitFunctions(false); + writer.PopBlock(); + writer.PopBlock(); + + if (useInternals) { - if (TypeInfo.SkippedFunctions.Contains(fd.Name)) { continue; } + writer.PushBlock($"namespace {projectNamespace}{InternalNamespace}"); + writer.PushBlock($"public static unsafe partial class {classPrefix}"); + EmitFunctions(true); + writer.PopBlock(); + writer.PopBlock(); + } - foreach (OverloadDefinition overload in fd.Overloads) + void EmitFunctions(bool isInternal) + { + foreach (FunctionDefinition fd in defs.Functions) { - string exportedName = overload.ExportedName; - if (exportedName.StartsWith("ig")) - { - exportedName = exportedName.Substring(2, exportedName.Length - 2); - } - if (exportedName.Contains("~")) { continue; } - if (overload.Parameters.Any(tr => tr.Type.Contains('('))) { continue; } // TODO: Parse function pointer parameters. + if (TypeInfo.SkippedFunctions.Contains(fd.Name)) { continue; } - bool hasVaList = false; - for (int i = 0; i < overload.Parameters.Length; i++) + foreach (OverloadDefinition overload in fd.Overloads.Where(o => !o.IsMemberFunction && o.IsInternal == isInternal)) { - TypeReference p = overload.Parameters[i]; - string paramType = GetTypeString(p.Type, p.IsFunctionPointer); - if (p.Name == "...") { continue; } + string exportedName = overload.ExportedName; + if (exportedName.StartsWith("ig")) + { + exportedName = exportedName.Substring(2, exportedName.Length - 2); + } + if (exportedName.Contains("~")) { continue; } + if (overload.Parameters.Any(tr => tr.Type.Contains('('))) { continue; } // TODO: Parse function pointer parameters. - if (paramType == "va_list") + bool hasVaList = false; + for (int i = 0; i < overload.Parameters.Length; i++) { - hasVaList = true; - break; + TypeReference p = overload.Parameters[i]; + string paramType = GetTypeString(p.Type, p.IsFunctionPointer); + if (p.Name == "...") { continue; } + + if (paramType == "va_list") + { + hasVaList = true; + break; + } } - } - if (hasVaList) { continue; } + if (hasVaList) { continue; } - KeyValuePair[] orderedDefaults = overload.DefaultValues.OrderByDescending( - kvp => GetIndex(overload.Parameters, kvp.Key)).ToArray(); + KeyValuePair[] orderedDefaults = overload.DefaultValues.OrderByDescending( + kvp => GetIndex(overload.Parameters, kvp.Key)).ToArray(); - for (int i = overload.DefaultValues.Count; i >= 0; i--) - { - if (overload.IsMemberFunction) { continue; } - Dictionary defaults = new Dictionary(); - for (int j = 0; j < i; j++) + for (int i = overload.DefaultValues.Count; i >= 0; i--) { - defaults.Add(orderedDefaults[j].Key, orderedDefaults[j].Value); + Dictionary defaults = new Dictionary(); + for (int j = 0; j < i; j++) + { + defaults.Add(orderedDefaults[j].Key, orderedDefaults[j].Value); + } + EmitOverload(writer, overload, defaults, null, classPrefix); } - EmitOverload(writer, overload, defaults, null, classPrefix); } } } - writer.PopBlock(); - writer.PopBlock(); } foreach (var method in defs.Variants) @@ -415,6 +481,8 @@ namespace CodeGenerator if (!variant.Used) Console.WriteLine($"Error: Variants targetting parameter {variant.Name} with type {variant.OriginalType} could not be applied to method {method.Key}."); } } + + return 0; } private static bool IsStringFieldName(string name) diff --git a/src/CodeGenerator/TypeInfo.cs b/src/CodeGenerator/TypeInfo.cs index 2f37689..034e66e 100644 --- a/src/CodeGenerator/TypeInfo.cs +++ b/src/CodeGenerator/TypeInfo.cs @@ -55,6 +55,17 @@ namespace CodeGenerator { "ImPlotFormatter", "IntPtr" }, { "ImPlotGetter", "IntPtr" }, { "ImPlotTransform", "IntPtr" }, + // internals + { "char[5]", "byte*"}, + { "ImGuiDir*", "IntPtr" }, + //{ "ImGuiStoragePair", "IntPtr" }, + { "ImGuiDockRequest", "IntPtr" }, + { "ImGuiDockNodeSettings", "IntPtr" }, + { "ImGuiTableColumnIdx", "sbyte" }, + { "ImGuiTableDrawChannelIdx", "byte"}, + { "ImGuiContextHookCallback", "IntPtr" }, + { "ImGuiErrorLogCallback", "IntPtr" }, + //{ "ImGuiSizeCallback", "IntPtr"} }; public static readonly List WellKnownEnums = new List() @@ -73,6 +84,7 @@ namespace CodeGenerator "ImVec2", "ImVec4", "ImGuiStoragePair", + "ImGuiStyleMod", }; public static readonly Dictionary WellKnownDefaultValues = new Dictionary()