From b04bf1d3e503d0c0a48c082654d83d3eb06ede5d Mon Sep 17 00:00:00 2001 From: Joao Matos Date: Sat, 4 Feb 2023 10:18:50 +0000 Subject: [PATCH 1/3] Rewrite property detection logic to be more configurable. --- src/CLI/Options.cs | 3 + src/Generator/Options.cs | 3 + .../Passes/GetterSetterToPropertyPass.cs | 69 +++++++++++++++---- 3 files changed, 63 insertions(+), 12 deletions(-) diff --git a/src/CLI/Options.cs b/src/CLI/Options.cs index 8a656f70b..00dfd700d 100644 --- a/src/CLI/Options.cs +++ b/src/CLI/Options.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using CppSharp.Generators; +using CppSharp.Passes; namespace CppSharp { @@ -43,6 +44,8 @@ class Options public GeneratorKind Kind { get; set; } = GeneratorKind.CSharp; + public PropertyDetectionMode PropertyMode = PropertyDetectionMode.Keywords; + public bool CheckSymbols { get; set; } public bool UnityBuild { get; set; } diff --git a/src/Generator/Options.cs b/src/Generator/Options.cs index f42236cd6..fe90828fb 100644 --- a/src/Generator/Options.cs +++ b/src/Generator/Options.cs @@ -247,6 +247,9 @@ public bool GenerateSingleCSharpFile /// public HashSet ExplicitlyPatchedVirtualFunctions { get; } + public PropertyDetectionMode PropertyDetectionMode { get; set; } = PropertyDetectionMode.Dictionary; + + [Obsolete("Use PropertyDetectionMode instead")] public bool UsePropertyDetectionHeuristics { get; set; } = true; /// diff --git a/src/Generator/Passes/GetterSetterToPropertyPass.cs b/src/Generator/Passes/GetterSetterToPropertyPass.cs index 6de31147e..5ec05eb08 100644 --- a/src/Generator/Passes/GetterSetterToPropertyPass.cs +++ b/src/Generator/Passes/GetterSetterToPropertyPass.cs @@ -11,6 +11,33 @@ namespace CppSharp.Passes { + /// + /// This is used by GetterSetterToPropertyPass to decide how to process + /// getter/setter class methods into properties. + /// + public enum PropertyDetectionMode + { + /// + /// No methods are converted to properties. + /// + None, + /// + /// All compatible methods are converted to properties. + /// + All, + /// + /// Only methods starting with certain keyword are converted to properties. + /// Right now we consider getter methods starting with "get", "is" and "has". + /// + Keywords, + /// + /// Heuristics based mode that uses english dictionary words to decide + /// if a getter method is an action and thus not to be considered as a + /// property. + /// + Dictionary + } + public class GetterSetterToPropertyPass : TranslationUnitPass { static GetterSetterToPropertyPass() @@ -44,6 +71,9 @@ public GetterSetterToPropertyPass() public override bool VisitClassDecl(Class @class) { + if (Options.PropertyDetectionMode == PropertyDetectionMode.None) + return false; + if (!base.VisitClassDecl(@class)) return false; @@ -86,22 +116,16 @@ protected IEnumerable GenerateProperties(Class @class) private IEnumerable CleanUp(Class @class, List properties) { - if (!Options.UsePropertyDetectionHeuristics) +#pragma warning disable CS0618 + if (!Options.UsePropertyDetectionHeuristics || +#pragma warning restore CS0618 + Options.PropertyDetectionMode == PropertyDetectionMode.All) return properties; for (int i = properties.Count - 1; i >= 0; i--) { - Property property = properties[i]; - if (property.HasSetter || property.IsExplicitlyGenerated) - continue; - - string firstWord = GetFirstWord(property.GetMethod.Name); - if (firstWord.Length < property.GetMethod.Name.Length && - Match(firstWord, new[] { "get", "is", "has" })) - continue; - - if (Match(firstWord, new[] { "to", "new", "on" }) || - Verbs.Contains(firstWord)) + var property = properties[i]; + if (!KeepProperty(property)) { property.GetMethod.GenerationKind = GenerationKind.Generate; @class.Properties.Remove(property); @@ -112,6 +136,27 @@ private IEnumerable CleanUp(Class @class, List properties) return properties; } + public virtual bool KeepProperty(Property property) + { + if (property.HasSetter || property.IsExplicitlyGenerated) + return true; + + var firstWord = GetFirstWord(property.GetMethod.Name); + var isKeyword = firstWord.Length < property.GetMethod.Name.Length && + Match(firstWord, new[] {"get", "is", "has"}); + + switch (Options.PropertyDetectionMode) + { + case PropertyDetectionMode.Keywords: + return isKeyword; + case PropertyDetectionMode.Dictionary: + var isAction = Match(firstWord, new[] {"to", "new", "on"}) || Verbs.Contains(firstWord); + return isKeyword || !isAction; + default: + return false; + } + } + private static void CreateOrUpdateProperty(List properties, Method method, string name, QualifiedType type, bool isSetter = false) { From 63ca51ae9a3eb33f2409ddbaf1e6e2251f5949ee Mon Sep 17 00:00:00 2001 From: Joao Matos Date: Sat, 4 Feb 2023 23:15:00 +0000 Subject: [PATCH 2/3] Add new property modes to the CLI driver. --- src/CLI/CLI.cs | 23 +++++++++++++++++++++++ src/CLI/Generator.cs | 1 + src/CLI/Options.cs | 2 +- 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/CLI/CLI.cs b/src/CLI/CLI.cs index a589bbf49..6a31df01c 100644 --- a/src/CLI/CLI.cs +++ b/src/CLI/CLI.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using CppSharp.Generators; +using CppSharp.Passes; using Mono.Options; namespace CppSharp @@ -34,6 +35,7 @@ static bool ParseCommandLineArgs(string[] args, List errorMessages, ref optionSet.Add("p=|platform=", "the {PLATFORM} that the generated code will target: 'win', 'osx' or 'linux' or 'emscripten'", p => { GetDestinationPlatform(p, errorMessages); }); optionSet.Add("a=|arch=", "the {ARCHITECTURE} that the generated code will target: 'x86' or 'x64' or 'wasm32' or 'wasm64'", a => { GetDestinationArchitecture(a, errorMessages); }); optionSet.Add("prefix=", "sets a string prefix to the names of generated files", a => { options.Prefix = a; }); + optionSet.Add("property=", "the property detection mode to use: 'all', 'none' or 'keywords' or 'heuristics'", p => { GetPropertyMode(p, errorMessages); }); optionSet.Add("exceptions", "enables support for C++ exceptions in the parser", v => { options.EnableExceptions = true; }); optionSet.Add("rtti", "enables support for C++ RTTI in the parser", v => { options.EnableRTTI = true; }); @@ -269,6 +271,27 @@ public static void GetDestinationArchitecture(string architecture, List Defaulting to {options.Architecture}"); } + static void GetPropertyMode(string mode, List errorMessages) + { + switch (mode.ToLower()) + { + case "all": + options.PropertyMode = PropertyDetectionMode.All; + return; + case "none": + options.PropertyMode = PropertyDetectionMode.None; + return; + case "dictionary": + options.PropertyMode = PropertyDetectionMode.Dictionary; + return; + case "keywords": + options.PropertyMode = PropertyDetectionMode.Keywords; + return; + } + + errorMessages.Add($"Unknown property detection mode: {mode}. Defaulting to {options.PropertyMode}"); + } + static void PrintErrorMessages(List errorMessages) { foreach (string m in errorMessages) diff --git a/src/CLI/Generator.cs b/src/CLI/Generator.cs index 4da7c5380..ab90cf9b6 100644 --- a/src/CLI/Generator.cs +++ b/src/CLI/Generator.cs @@ -129,6 +129,7 @@ public void Setup(Driver driver) var driverOptions = driver.Options; driverOptions.GeneratorKind = options.Kind; + driverOptions.PropertyDetectionMode = options.PropertyMode; var module = driverOptions.AddModule(options.OutputFileName); if (!string.IsNullOrEmpty(options.InputLibraryName)) diff --git a/src/CLI/Options.cs b/src/CLI/Options.cs index 00dfd700d..239093879 100644 --- a/src/CLI/Options.cs +++ b/src/CLI/Options.cs @@ -44,7 +44,7 @@ class Options public GeneratorKind Kind { get; set; } = GeneratorKind.CSharp; - public PropertyDetectionMode PropertyMode = PropertyDetectionMode.Keywords; + public PropertyDetectionMode PropertyMode { get; set; } = PropertyDetectionMode.Keywords; public bool CheckSymbols { get; set; } From 59e9b91a82cca2fdef1a63be81b8354dfd755a3f Mon Sep 17 00:00:00 2001 From: Joao Matos Date: Sun, 5 Feb 2023 12:44:19 +0000 Subject: [PATCH 3/3] Refactor property handling in GetterSetterToProperty. --- .../Passes/GetterSetterToPropertyPass.cs | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/Generator/Passes/GetterSetterToPropertyPass.cs b/src/Generator/Passes/GetterSetterToPropertyPass.cs index 5ec05eb08..e6c6dde46 100644 --- a/src/Generator/Passes/GetterSetterToPropertyPass.cs +++ b/src/Generator/Passes/GetterSetterToPropertyPass.cs @@ -125,12 +125,12 @@ private IEnumerable CleanUp(Class @class, List properties) for (int i = properties.Count - 1; i >= 0; i--) { var property = properties[i]; - if (!KeepProperty(property)) - { - property.GetMethod.GenerationKind = GenerationKind.Generate; - @class.Properties.Remove(property); - properties.RemoveAt(i); - } + if (KeepProperty(property)) + continue; + + property.GetMethod.GenerationKind = GenerationKind.Generate; + @class.Properties.Remove(property); + properties.RemoveAt(i); } return properties; @@ -160,6 +160,14 @@ public virtual bool KeepProperty(Property property) private static void CreateOrUpdateProperty(List properties, Method method, string name, QualifiedType type, bool isSetter = false) { + string NormalizeName(string name) + { + return string.IsNullOrEmpty(name) ? + name : string.Concat(char.ToLowerInvariant(name[0]), name.Substring(1)); + } + + var normalizedName = NormalizeName(name); + Type underlyingType = GetUnderlyingType(type); Property property = properties.Find( p => p.Field == null && @@ -169,10 +177,10 @@ private static void CreateOrUpdateProperty(List properties, Method met p.GetMethod.OriginalReturnType).Equals(underlyingType)) || (p.HasSetter && GetUnderlyingType( p.SetMethod.Parameters[0].QualifiedType).Equals(underlyingType))) && - Match(p, name)); + Match(p, normalizedName)); if (property == null) - properties.Add(property = new Property { Name = name, QualifiedType = type }); + properties.Add(property = new Property { Name = normalizedName, QualifiedType = type }); method.AssociatedDeclaration = property; @@ -246,7 +254,9 @@ private static void ProcessProperties(Class @class, IEnumerable proper property.SetMethod.OriginalReturnType.Type.Desugar().IsPrimitiveType(PrimitiveType.Void)) property.SetMethod.GenerationKind = GenerationKind.Internal; property.Namespace = @class; + @class.Properties.Add(property); + RenameConflictingMethods(@class, property); CombineComments(property); } @@ -339,14 +349,8 @@ private static string GetPropertyName(string name) (string.Compare(name, firstWord, StringComparison.InvariantCultureIgnoreCase) == 0) || char.IsNumber(name[3])) return name; - if (name.Length == 4) - { - return char.ToLowerInvariant( - name[3]).ToString(CultureInfo.InvariantCulture); - } - - return string.Concat(char.ToLowerInvariant( - name[3]).ToString(CultureInfo.InvariantCulture), name.AsSpan(4)); + var rest = (name.Length == 4) ? string.Empty : name.Substring(4); + return string.Concat(name[3], rest); } private static string GetPropertyNameFromSetter(string name) @@ -359,7 +363,6 @@ private static string GetPropertyNameFromSetter(string name) return nameBuilder.ToString(); nameBuilder.TrimUnderscores(); - nameBuilder[0] = char.ToLowerInvariant(nameBuilder[0]); return nameBuilder.ToString(); }