using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions; using CppSharp.AST; using CppSharp.AST.Extensions; namespace CppSharp.Passes { /// /// Base class for transform that perform renames of declarations. /// public abstract class RenamePass : TranslationUnitPass { public class ParameterComparer : IEqualityComparer { public bool Equals(Parameter x, Parameter y) { return x.QualifiedType == y.QualifiedType && x.GenerationKind == y.GenerationKind; } public int GetHashCode(Parameter obj) { return obj.Type.GetHashCode(); } } public RenameTargets Targets = RenameTargets.Any; protected RenamePass() { } protected RenamePass(RenameTargets targets) { Targets = targets; } public virtual bool Rename(Declaration decl, out string newName) { var method = decl as Method; if (method != null && !method.IsStatic) { var rootBaseMethod = ((Class) method.Namespace).GetRootBaseMethod(method); if (rootBaseMethod != null && rootBaseMethod != method) { newName = rootBaseMethod.Name; return true; } } var property = decl as Property; if (property != null && !property.IsStatic) { var rootBaseProperty = ((Class) property.Namespace).GetRootBaseProperty(property); if (rootBaseProperty != null && rootBaseProperty != property) { newName = rootBaseProperty.Name; return true; } } newName = decl.Name; return false; } public bool IsRenameableDecl(Declaration decl) { if (decl is Class) return true; if (decl is Field) return true; var function = decl as Function; if (function != null) { // special case the IDisposable.Dispose that could be added later return !function.IsOperator && function.Name != "dispose"; } if (decl is Parameter) return true; if (decl is Enumeration) return true; if (decl is Property) return true; if (decl is Event) return true; if (decl is TypedefDecl) return true; if (decl is Namespace && !(decl is TranslationUnit)) return true; if (decl is Variable) return true; return false; } public override bool VisitClassDecl(Class @class) { if (@class.IsDynamic) { // HACK: entries in v-tables are not shared (as objects) with the virtual methods they represent; // this is why this pass has to rename entries in the v-table as well; // this should be fixed in the parser: it should reuse method objects foreach (var method in VTables.GatherVTableMethodEntries(@class).Where( e => e.Method != null && IsRenameableDecl(e.Method)).Select(e => e.Method)) { Rename(method); } } return base.VisitClassDecl(@class); } public override bool VisitDeclaration(Declaration decl) { if (AlreadyVisited(decl)) return false; if (!IsRenameableDecl(decl)) return true; if (decl.Name == null) return true; return Rename(decl); } private bool Rename(Declaration decl) { string newName; if (Rename(decl, out newName) && !AreThereConflicts(decl, newName)) decl.Name = newName; return true; } private static bool AreThereConflicts(Declaration decl, string newName) { var declarations = new List(); declarations.AddRange(decl.Namespace.Classes.Where(c => !c.IsIncomplete)); declarations.AddRange(decl.Namespace.Enums); declarations.AddRange(decl.Namespace.Events); var function = decl as Function; if (function != null && function.SynthKind != FunctionSynthKind.AdjustedMethod) { // account for overloads declarations.AddRange(GetFunctionsWithTheSameParams(function)); } else declarations.AddRange(decl.Namespace.Functions); declarations.AddRange(decl.Namespace.Variables); declarations.AddRange(from typedefDecl in decl.Namespace.Typedefs let pointerType = typedefDecl.Type.Desugar() as PointerType where pointerType != null && pointerType.Pointee is FunctionType select typedefDecl); var result = declarations.Any(d => d != decl && d.Name == newName); if (result) return true; var method = decl as Method; if (method == null || !method.IsGenerated) return false; return ((Class) method.Namespace).GetPropertyByName(newName) != null; } private static IEnumerable GetFunctionsWithTheSameParams(Function function) { var method = function as Method; if (method != null) { return ((Class) method.Namespace).Methods.Where( m => m.Parameters.SequenceEqual(function.Parameters, new ParameterComparer())); } return function.Namespace.Functions.Where( f => f.Parameters.SequenceEqual(function.Parameters, new ParameterComparer())); } public override bool VisitEnumItem(Enumeration.Item item) { if (!Targets.HasFlag(RenameTargets.EnumItem)) return false; string newName; if (Rename(item, out newName)) { item.Name = newName; return true; } return true; } public override bool VisitFieldDecl(Field field) { if (!Targets.HasFlag(RenameTargets.Field)) return false; return base.VisitFieldDecl(field); } public override bool VisitProperty(Property property) { if (!Targets.HasFlag(RenameTargets.Property)) return false; return base.VisitProperty(property); } public override bool VisitTypedefDecl(TypedefDecl typedef) { if (!Targets.HasFlag(RenameTargets.Delegate)) return false; return base.VisitTypedefDecl(typedef); } public override bool VisitMethodDecl(Method method) { if (!Targets.HasFlag(RenameTargets.Method)) return false; if (method.Kind != CXXMethodKind.Normal) return false; return base.VisitMethodDecl(method); } public override bool VisitFunctionDecl(Function function) { if (!Targets.HasFlag(RenameTargets.Function)) return false; return base.VisitFunctionDecl(function); } public override bool VisitParameterDecl(Parameter parameter) { if (!Targets.HasFlag(RenameTargets.Parameter)) return false; return base.VisitParameterDecl(parameter); } public override bool VisitEvent(Event @event) { if (!Targets.HasFlag(RenameTargets.Event)) return false; return base.VisitEvent(@event); } public override bool VisitVariableDecl(Variable variable) { if (!Targets.HasFlag(RenameTargets.Variable)) return false; return base.VisitVariableDecl(variable); } } [Flags] public enum RenameTargets { Class = 1 << 0, Field = 1 << 1, Method = 1 << 2, Function = 1 << 3, Parameter = 1 << 4, Enum = 1 << 5, EnumItem = 1 << 6, Event = 1 << 7, Property = 1 << 8, Delegate = 1 << 9, Variable = 1 << 10, Any = Function | Method | Parameter | Class | Field | Enum | EnumItem | Event | Property | Delegate | Variable } /// /// Renames a declaration based on a regular expression pattern. /// public class RegexRenamePass : RenamePass { public string Pattern; public string Replacement; public RegexRenamePass(string pattern, string replacement) { Pattern = pattern; Replacement = replacement; } public RegexRenamePass(string pattern, string replacement, RenameTargets targets) : this(pattern, replacement) { Targets = targets; } public override bool Rename(Declaration decl, out string newName) { if (base.Rename(decl, out newName)) return true; var replace = Regex.Replace(decl.Name, Pattern, Replacement); if (!decl.Name.Equals(replace)) { newName = replace; return true; } newName = null; return false; } } public enum RenameCasePattern { UpperCamelCase, LowerCamelCase } /// /// Renames a declaration based on a regular expression pattern. /// public class CaseRenamePass : RenamePass { public RenameCasePattern Pattern; public CaseRenamePass(RenameTargets targets, RenameCasePattern pattern) : base(targets) { Pattern = pattern; } public override bool Rename(Declaration decl, out string newName) { if (base.Rename(decl, out newName)) return true; switch (Pattern) { case RenameCasePattern.LowerCamelCase: newName = ConvertCaseString(decl.Name, RenameCasePattern.LowerCamelCase); return true; case RenameCasePattern.UpperCamelCase: newName = ConvertCaseString(decl.Name, RenameCasePattern.UpperCamelCase); return true; } return false; } /// /// Converts the phrase to specified convention. /// /// /// The cases. /// string static string ConvertCaseString(string phrase, RenameCasePattern pattern) { // check if it's been renamed to avoid a keyword if (phrase.StartsWith("@")) phrase = phrase.Substring(1); var splittedPhrase = phrase.Split(' ', '-', '.'); var sb = new StringBuilder(); switch (pattern) { case RenameCasePattern.LowerCamelCase: sb.Append(splittedPhrase[0].ToLower()); splittedPhrase[0] = string.Empty; break; case RenameCasePattern.UpperCamelCase: sb = new StringBuilder(); break; } foreach (var s in splittedPhrase) { var splittedPhraseChars = s.ToCharArray(); if (splittedPhraseChars.Length > 0) { var str = new String(splittedPhraseChars[0], 1); splittedPhraseChars[0] = (str.ToUpper().ToCharArray())[0]; } sb.Append(new String(splittedPhraseChars)); } return sb.ToString(); } } public static class RenamePassExtensions { public static void RenameWithPattern(this PassBuilder builder, string pattern, string replacement, RenameTargets targets) { builder.AddPass(new RegexRenamePass(pattern, replacement, targets)); } public static void RemovePrefix(this PassBuilder builder, string prefix, RenameTargets targets = RenameTargets.Any) { builder.AddPass(new RegexRenamePass("^" + prefix, string.Empty, targets)); } public static void RenameDeclsCase(this PassBuilder builder, RenameTargets targets, RenameCasePattern pattern) { builder.AddPass(new CaseRenamePass(targets, pattern)); } public static void RenameDeclsUpperCase(this PassBuilder builder, RenameTargets targets) { builder.AddPass(new CaseRenamePass(targets, RenameCasePattern.UpperCamelCase)); } public static void RenameDeclsLowerCase(this PassBuilder builder, RenameTargets targets) { builder.AddPass(new CaseRenamePass(targets, RenameCasePattern.LowerCamelCase)); } } }