using System; using System.Collections.Generic; using EpicGames.Core; using EpicGames.UHT.Types; using UnrealSharpScriptGenerator.PropertyTranslators; namespace UnrealSharpScriptGenerator.Utilities; public enum ENameType { Parameter, Property, Struct, Function } public static class NameMapper { private static readonly List ReservedKeywords = new() { "abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char", "checked", "class", "const", "continue", "decimal", "default", "delegate", "do", "double", "else", "enum", "event", "explicit", "extern", "false", "finally", "fixed", "float", "for", "foreach", "goto", "if", "implicit", "in", "int", "interface", "internal", "is", "lock", "long", "namespace", "new", "null", "object", "operator", "out", "override", "params", "private", "protected", "public", "readonly", "ref", "return", "sbyte", "sealed", "short", "sizeof", "stackalloc", "static", "string", "struct", "switch", "this", "throw", "true", "try", "typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", "using", "virtual", "void", "volatile", "while", "System" }; public static string GetParameterName(this UhtProperty property) { string scriptName = ScriptifyName(property.GetScriptName(), ENameType.Parameter); if (property.Outer is not UhtFunction function) { return scriptName; } foreach (UhtProperty exportedProperty in function.Properties) { if (exportedProperty.HasAllFlags(EPropertyFlags.ReturnParm)) { continue; } if (exportedProperty != property && scriptName == ScriptifyName(exportedProperty.GetScriptName(), ENameType.Parameter)) { return PascalToCamelCase(exportedProperty.SourceName); } } return scriptName; } public static string GetPropertyName(this UhtProperty property) { string propertyName = ScriptifyName(property.GetScriptName(), ENameType.Property); if (property.Outer!.SourceName == propertyName || IsAKeyword(propertyName)) { propertyName = $"K2_{propertyName}"; } return TryResolveConflictingName(property, propertyName); } public static string GetStructName(this UhtType type) { if (type.EngineType is UhtEngineType.Interface or UhtEngineType.NativeInterface || type == Program.Factory.Session.UInterface) { return "I" + type.EngineName; } if (type is UhtClass uhtClass && uhtClass.IsChildOf(Program.BlueprintFunctionLibrary)) { return type.GetScriptName(); } return type.SourceName; } public static string ExportGetAssemblyName(this UhtType type) { string structName = type.GetStructName(); return $"typeof({structName}).GetAssemblyName()"; } public static string GetFullManagedName(this UhtType type) { return $"{type.GetNamespace()}.{type.GetStructName()}"; } static readonly string[] MetadataKeys = { "ScriptName", "ScriptMethod", "DisplayName" }; public static string GetScriptName(this UhtType type) { bool OnlyContainsLetters(string str) { foreach (char c in str) { if (!char.IsLetter(c) && !char.IsWhiteSpace(c)) { return false; } } return true; } foreach (var key in MetadataKeys) { string value = type.GetMetadata(key); if (string.IsNullOrEmpty(value) || !OnlyContainsLetters(value)) { continue; } // Try remove whitespace from the value value = value.Replace(" ", ""); return value; } return type.SourceName; } public static string GetNamespace(this UhtType typeObj) { UhtType outer = typeObj; string packageShortName = ""; if (outer is UhtPackage package) { packageShortName = package.GetShortName(); } else { while (outer.Outer != null) { outer = outer.Outer; if (outer is UhtPackage header) { packageShortName = header.Package.GetShortName(); break; } } } if (string.IsNullOrEmpty(packageShortName)) { throw new Exception($"Failed to find package name for {typeObj}"); } return $"UnrealSharp.{packageShortName}"; } public static string GetFunctionName(this UhtFunction function) { string functionName = function.GetScriptName(); if (function.HasAnyFlags(EFunctionFlags.Delegate | EFunctionFlags.MulticastDelegate)) { functionName = DelegateBasePropertyTranslator.GetDelegateName(function); } if (functionName.StartsWith("K2_") || functionName.StartsWith("BP_")) { functionName = functionName.Substring(3); } if (function.IsInterfaceFunction() && functionName.EndsWith("_Implementation")) { functionName = functionName.Substring(0, functionName.Length - 15); } if (function.Outer is not UhtClass) { return functionName; } functionName = TryResolveConflictingName(function, functionName); return functionName; } public static string TryResolveConflictingName(UhtType type, string scriptName) { UhtType outer = type.Outer!; bool IsConflictingWithChild(List children) { foreach (UhtType child in children) { if (child == type) { continue; } if (child is UhtProperty property) { if (scriptName == ScriptifyName(property.GetScriptName(), ENameType.Property)) { return true; } } if (child is UhtFunction function) { if (scriptName == ScriptifyName(function.GetScriptName(), ENameType.Function)) { return true; } } } return false; } bool isConflicting = IsConflictingWithChild(outer.Children); if (!isConflicting && outer is UhtClass outerClass) { List classInterfaces = outerClass.GetInterfaces(); foreach (UhtClass classInterface in classInterfaces) { if (classInterface.AlternateObject is not UhtClass interfaceClass) { continue; } UhtFunction? function = interfaceClass.FindFunctionByName(scriptName, (uhtFunction, s) => uhtFunction.GetFunctionName() == s); if (function != null && type is UhtFunction typeAsFunction) { if (function.HasSameSignature(typeAsFunction)) { continue; } isConflicting = true; break; } } } return isConflicting ? type.EngineName : scriptName; } public static string ScriptifyName(string engineName, ENameType nameType) { string strippedName = engineName; switch (nameType) { case ENameType.Parameter: strippedName = StripPropertyPrefix(strippedName); strippedName = PascalToCamelCase(strippedName); break; case ENameType.Property: strippedName = StripPropertyPrefix(strippedName); break; case ENameType.Struct: break; case ENameType.Function: break; default: throw new ArgumentOutOfRangeException(nameof(nameType), nameType, null); } return EscapeKeywords(strippedName); } public static string StripPropertyPrefix(string inName) { int nameOffset = 0; while (true) { // Strip the "b" prefix from bool names if (inName.Length - nameOffset >= 2 && inName[nameOffset] == 'b' && char.IsUpper(inName[nameOffset + 1])) { nameOffset += 1; continue; } // Strip the "In" prefix from names if (inName.Length - nameOffset >= 3 && inName[nameOffset] == 'I' && inName[nameOffset + 1] == 'n' && char.IsUpper(inName[nameOffset + 2])) { nameOffset += 2; continue; } break; } return nameOffset != 0 ? inName.Substring(nameOffset) : inName; } public static string EscapeKeywords(string name) { return IsAKeyword(name) || char.IsDigit(name[0]) ? $"_{name}" : name; } private static bool IsAKeyword(string name) { return ReservedKeywords.Contains(name); } private static string PascalToCamelCase(string name) { return char.ToLowerInvariant(name[0]) + name.Substring(1); } }