308 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			308 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| 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<string> 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<UhtType> 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<UhtClass> 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);
 | |
|     }
 | |
| } |