Skip to content

Commit

Permalink
Rework property handling (#1714)
Browse files Browse the repository at this point in the history
* Rewrite property detection logic to be more configurable.

* Add new property modes to the CLI driver.

* Refactor property handling in GetterSetterToProperty.
  • Loading branch information
tritao authored Sep 3, 2024
1 parent de0b0ba commit 45d6b32
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 26 deletions.
23 changes: 23 additions & 0 deletions src/CLI/CLI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.IO;
using System.Linq;
using CppSharp.Generators;
using CppSharp.Passes;
using Mono.Options;

namespace CppSharp
Expand Down Expand Up @@ -34,6 +35,7 @@ static bool ParseCommandLineArgs(string[] args, List<string> 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; });
Expand Down Expand Up @@ -269,6 +271,27 @@ public static void GetDestinationArchitecture(string architecture, List<string>
Defaulting to {options.Architecture}");
}

static void GetPropertyMode(string mode, List<string> 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<string> errorMessages)
{
foreach (string m in errorMessages)
Expand Down
1 change: 1 addition & 0 deletions src/CLI/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
3 changes: 3 additions & 0 deletions src/CLI/Options.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using CppSharp.Generators;
using CppSharp.Passes;

namespace CppSharp
{
Expand Down Expand Up @@ -43,6 +44,8 @@ class Options

public GeneratorKind Kind { get; set; } = GeneratorKind.CSharp;

public PropertyDetectionMode PropertyMode { get; set; } = PropertyDetectionMode.Keywords;

public bool CheckSymbols { get; set; }

public bool UnityBuild { get; set; }
Expand Down
3 changes: 3 additions & 0 deletions src/Generator/Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,9 @@ public bool GenerateSingleCSharpFile
/// </summary>
public HashSet<string> ExplicitlyPatchedVirtualFunctions { get; }

public PropertyDetectionMode PropertyDetectionMode { get; set; } = PropertyDetectionMode.Dictionary;

[Obsolete("Use PropertyDetectionMode instead")]
public bool UsePropertyDetectionHeuristics { get; set; } = true;

/// <summary>
Expand Down
100 changes: 74 additions & 26 deletions src/Generator/Passes/GetterSetterToPropertyPass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,33 @@

namespace CppSharp.Passes
{
/// <summary>
/// This is used by GetterSetterToPropertyPass to decide how to process
/// getter/setter class methods into properties.
/// </summary>
public enum PropertyDetectionMode
{
/// <summary>
/// No methods are converted to properties.
/// </summary>
None,
/// <summary>
/// All compatible methods are converted to properties.
/// </summary>
All,
/// <summary>
/// Only methods starting with certain keyword are converted to properties.
/// Right now we consider getter methods starting with "get", "is" and "has".
/// </summary>
Keywords,
/// <summary>
/// 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.
/// </summary>
Dictionary
}

public class GetterSetterToPropertyPass : TranslationUnitPass
{
static GetterSetterToPropertyPass()
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -86,35 +116,58 @@ protected IEnumerable<Property> GenerateProperties(Class @class)

private IEnumerable<Property> CleanUp(Class @class, List<Property> 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" }))
var property = properties[i];
if (KeepProperty(property))
continue;

if (Match(firstWord, new[] { "to", "new", "on" }) ||
Verbs.Contains(firstWord))
{
property.GetMethod.GenerationKind = GenerationKind.Generate;
@class.Properties.Remove(property);
properties.RemoveAt(i);
}
property.GetMethod.GenerationKind = GenerationKind.Generate;
@class.Properties.Remove(property);
properties.RemoveAt(i);
}

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<Property> 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 &&
Expand All @@ -124,10 +177,10 @@ private static void CreateOrUpdateProperty(List<Property> 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;

Expand Down Expand Up @@ -201,7 +254,9 @@ private static void ProcessProperties(Class @class, IEnumerable<Property> 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);
}
Expand Down Expand Up @@ -294,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)
Expand All @@ -314,7 +363,6 @@ private static string GetPropertyNameFromSetter(string name)
return nameBuilder.ToString();

nameBuilder.TrimUnderscores();
nameBuilder[0] = char.ToLowerInvariant(nameBuilder[0]);
return nameBuilder.ToString();
}

Expand Down

0 comments on commit 45d6b32

Please sign in to comment.