-
Notifications
You must be signed in to change notification settings - Fork 304
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add option for handling internals #364
base: master
Are you sure you want to change the base?
Changes from 2 commits
2bedfd5
70a7b35
05714ca
9368cc8
2c2bc9b
5c45b25
10f5f90
d503ac0
4a45326
02a1a7f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,37 +8,83 @@ | |
using System.Text; | ||
using System.Text.RegularExpressions; | ||
using System.Threading.Tasks; | ||
using System.CommandLine; | ||
|
||
namespace CodeGenerator | ||
{ | ||
internal static class Program | ||
{ | ||
static void Main(string[] args) | ||
private const string InternalNamespace = ".Internal"; | ||
|
||
static async Task<int> 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<DirectoryInfo>( | ||
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<string>( | ||
aliases: new[] { "--library", "-l" }, | ||
description: "The library to read parse.", | ||
getDefaultValue: () => "cimgui") | ||
.FromAmong("cimgui", "cimplot", "cimnodes", "cimguizmo"); | ||
|
||
var optionInternal = new Option<bool>( | ||
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 +124,7 @@ static void Main(string[] args) | |
|
||
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 +164,7 @@ static void Main(string[] args) | |
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 +330,10 @@ static void Main(string[] args) | |
{ | ||
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 +397,7 @@ static void Main(string[] args) | |
writer.PopBlock(); | ||
} | ||
|
||
// Root ImGui* class items - Noninternal | ||
using (CSharpCodeWriter writer = new CSharpCodeWriter(Path.Combine(outputPath, $"{classPrefix}.gen.cs"))) | ||
{ | ||
writer.Using("System"); | ||
|
@@ -360,52 +411,65 @@ static void Main(string[] args) | |
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(); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This runs the function overload processing logic twice, once for non-internals, to put things in the |
||
|
||
foreach (OverloadDefinition overload in fd.Overloads) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code was promoted to a local function so it could run twice, with the internal flag set or not. The code is the same except for line 433. Originally this method does a ton of processing on the member data only to do a final check, if it isn't a member function (and thus belongs on the |
||
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<string, string>[] orderedDefaults = overload.DefaultValues.OrderByDescending( | ||
kvp => GetIndex(overload.Parameters, kvp.Key)).ToArray(); | ||
KeyValuePair<string, string>[] orderedDefaults = overload.DefaultValues.OrderByDescending( | ||
kvp => GetIndex(overload.Parameters, kvp.Key)).ToArray(); | ||
|
||
for (int i = overload.DefaultValues.Count; i >= 0; i--) | ||
{ | ||
if (overload.IsMemberFunction) { continue; } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here is the line I removed and added to the main loop to filter out member functions. |
||
Dictionary<string, string> defaults = new Dictionary<string, string>(); | ||
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<string, string> defaults = new Dictionary<string, string>(); | ||
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 +479,8 @@ static void Main(string[] args) | |
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) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm unsure why github has decided to diff this in this way, but this is all new code apart from the declarations of the
outputPath
andlibraryName
variables which was moved up from below. You can see this code in raw format in this comment. This does a few things:https://github.com/mellinoe/ImGui.NET/blob/70a7b35459b260377b7487981b65839a02e3a8c6/src/CodeGenerator/Program.cs#L21-L87