Replace delimiters with unicode shapes

This commit is contained in:
2026-03-19 00:40:27 -04:00
parent 467c1464aa
commit e9ad41bbb3
9 changed files with 91 additions and 60 deletions

View File

@@ -77,7 +77,7 @@ WingFetcher& WingFetcher::Walk(const FString& Path)
if (bError) return *this;
TArray<FString> Segments;
Path.ParseIntoArray(Segments, TEXT(","));
Path.ParseIntoArray(Segments, TEXT(""));
if (Segments.Num() == 0)
{
UWingServer::Print(TEXT("ERROR: Empty path\n"));

View File

@@ -20,8 +20,8 @@ FString WingFunctionArgs::GetArgs(UEdGraphNode* Node)
TStringBuilder<256> SB;
for (const TSharedPtr<FUserPinInfo>& Pin : Editable->UserDefinedPins)
{
if (SB.Len() > 0) SB << TEXT(", ");
SB << UWingTypes::TypeToText(Pin->PinType) << TEXT(" ") << Pin->PinName.ToString();
if (SB.Len() > 0) SB << TEXT("");
SB << UWingTypes::TypeToText(Pin->PinType) << TEXT("") << Pin->PinName.ToString();
}
return FString(SB);
}
@@ -42,23 +42,23 @@ bool WingFunctionArgs::ParseArgs(const FString& Args, TArray<FParsedArg>& OutArg
if (Trimmed.IsEmpty()) return true;
TArray<FString> Parts;
Trimmed.ParseIntoArray(Parts, TEXT(","));
Trimmed.ParseIntoArray(Parts, TEXT(""));
for (const FString& Part : Parts)
{
FString Token = Part.TrimStartAndEnd();
if (Token.IsEmpty()) continue;
// Split "type name" on the last space.
int32 LastSpace;
if (!Token.FindLastChar(TEXT(' '), LastSpace))
// Split "typename" on the white bullet.
FString TypeStr, NameStr;
if (!Token.Split(TEXT(""), &TypeStr, &NameStr))
{
UWingServer::Printf(TEXT("ERROR: Expected 'type name' but got '%s'\n"), *Token);
UWingServer::Printf(TEXT("ERROR: Expected 'typename' but got '%s'\n"), *Token);
return false;
}
FString TypeStr = Token.Left(LastSpace).TrimStartAndEnd();
FString NameStr = Token.Mid(LastSpace + 1).TrimStartAndEnd();
TypeStr.TrimStartAndEndInline();
NameStr.TrimStartAndEndInline();
if (TypeStr.IsEmpty() || NameStr.IsEmpty())
{

View File

@@ -149,11 +149,11 @@ FString UWingTypes::TypeToTextInner(FName Category, FName SubCategory, UObject*
if (Category == UEdGraphSchema_K2::PC_Object)
return Short;
if (Category == UEdGraphSchema_K2::PC_Class)
return FString::Printf(TEXT("Class<%s>"), *Short);
return FString::Printf(TEXT("Class%s"), *Short);
if (Category == UEdGraphSchema_K2::PC_SoftObject)
return FString::Printf(TEXT("Soft<%s>"), *Short);
return FString::Printf(TEXT("Soft%s"), *Short);
if (Category == UEdGraphSchema_K2::PC_SoftClass)
return FString::Printf(TEXT("SoftClass<%s>"), *Short);
return FString::Printf(TEXT("SoftClass%s"), *Short);
if (Category == UEdGraphSchema_K2::PC_Interface)
return Short;
}
@@ -171,9 +171,9 @@ FString UWingTypes::TypeToText(const FEdGraphPinType& PinType)
return FString();
if (PinType.IsArray())
return FString::Printf(TEXT("Array<%s>"), *Inner);
return FString::Printf(TEXT("Array%s"), *Inner);
if (PinType.IsSet())
return FString::Printf(TEXT("Set<%s>"), *Inner);
return FString::Printf(TEXT("Set%s"), *Inner);
if (PinType.IsMap())
{
FString ValueInner = Types->TypeToTextInner(
@@ -182,7 +182,7 @@ FString UWingTypes::TypeToText(const FEdGraphPinType& PinType)
PinType.PinValueType.TerminalSubCategoryObject.Get());
if (ValueInner.IsEmpty())
return FString();
return FString::Printf(TEXT("Map<%s, %s>"), *Inner, *ValueInner);
return FString::Printf(TEXT("Map%s%s"), *Inner, *ValueInner);
}
return Inner;
@@ -428,29 +428,29 @@ bool UWingTypes::ParsePlainIdentifier(FEdGraphPinType& OutType)
bool UWingTypes::ParseWrapped(FName Wrapper, FEdGraphPinType& OutType)
{
Cursor++;
if (!ParseChar('<')) return false;
if (!ParseChar(L'')) return false;
if (!ParsePlainIdentifier(OutType)) return false;
if (!Cast<UClass>(OutType.PinSubCategoryObject))
{
Error = FString::Printf(TEXT("%s is not a Class"), *OutType.PinSubCategoryObject->GetName());
return false;
}
if (!ParseChar('>')) return false;
if (!ParseChar(L'')) return false;
OutType.PinCategory = Wrapper;
return true;
}
bool UWingTypes::ParseMaybeWrapped(FEdGraphPinType& OutType)
{
if (TokenIs(TEXT("Soft"), '<'))
if (TokenIs(TEXT("Soft"), L''))
{
return ParseWrapped(UEdGraphSchema_K2::PC_SoftObject, OutType);
}
else if (TokenIs(TEXT("Class"), '<'))
else if (TokenIs(TEXT("Class"), L''))
{
return ParseWrapped(UEdGraphSchema_K2::PC_Class, OutType);
}
else if (TokenIs(TEXT("SoftClass"), '<'))
else if (TokenIs(TEXT("SoftClass"), L''))
{
return ParseWrapped(UEdGraphSchema_K2::PC_SoftClass, OutType);
}
@@ -460,40 +460,40 @@ bool UWingTypes::ParseMaybeWrapped(FEdGraphPinType& OutType)
bool UWingTypes::ParseArrayOrSet(FEdGraphPinType& OutType)
{
Cursor++;
if (!ParseChar('<')) return false;
if (!ParseChar(L'')) return false;
if (!ParseMaybeWrapped(OutType)) return false;
if (!ParseChar('>')) return false;
if (!ParseChar(L'')) return false;
return true;
}
bool UWingTypes::ParseMap(FEdGraphPinType& OutType)
{
Cursor++;
if (!ParseChar('<')) return false;
if (!ParseChar(L'')) return false;
if (!ParsePlainIdentifier(OutType)) return false;
if (!ParseChar(',')) return false;
if (!ParseChar(L'')) return false;
FEdGraphPinType ValueType;
if (!ParseMaybeWrapped(ValueType)) return false;
OutType.PinValueType.TerminalCategory = ValueType.PinCategory;
OutType.PinValueType.TerminalSubCategory = ValueType.PinSubCategory;
OutType.PinValueType.TerminalSubCategoryObject = ValueType.PinSubCategoryObject;
if (!ParseChar('>')) return false;
if (!ParseChar(L'')) return false;
return true;
}
bool UWingTypes::ParseType(FEdGraphPinType& OutType)
{
if (TokenIs(TEXT("Array"), '<'))
if (TokenIs(TEXT("Array"), L''))
{
OutType.ContainerType = EPinContainerType::Array;
if (!ParseArrayOrSet(OutType)) return false;
}
else if (TokenIs(TEXT("Set"), '<'))
else if (TokenIs(TEXT("Set"), L''))
{
OutType.ContainerType = EPinContainerType::Set;
if (!ParseArrayOrSet(OutType)) return false;
}
else if (TokenIs(TEXT("Map"), '<'))
else if (TokenIs(TEXT("Map"), L''))
{
OutType.ContainerType = EPinContainerType::Map;
if (!ParseMap(OutType)) return false;

View File

@@ -51,7 +51,16 @@ extern int32 TrySavePackageSEH(
#endif
// ============================================================
// Name Formatting
// Name sanitization
//
// Our parsers use Unicode geometric shape delimiters that
// are unlikely to appear in names: ◂▸ for type brackets, ◆
// for map key◆value, ◦ for type◦name in prototypes, │ for
// list/path separation. If any of these characters appear
// in a name, we replace them with safe ASCII equivalents.
// One consequence of name sanitization is that we can't use
// Unreal's name-lookup routines — the LLM only sees
// sanitized names. So we do our own name lookups.
// ============================================================
void WingUtils::SanitizeNameInPlace(FString &Name)
@@ -61,7 +70,11 @@ void WingUtils::SanitizeNameInPlace(FString &Name)
{
TCHAR c = Name[Src];
if (c < 0x20 || c == 0x7F) continue;
if ((c == ' ') || (c == ',') || (c == ':')) c = '_';
if (c == L'') c='<';
if (c == L'') c='>';
if (c == L'') c='*';
if (c == L'') c='.';
if (c == L'') c = '|';
Name[Dst++] = c;
}
if (Dst == 0) Name[Dst++] = '_';
@@ -82,6 +95,10 @@ FString WingUtils::SanitizeName(FName Name)
return Result;
}
// ============================================================
// Name Lookup
// ============================================================
FString WingUtils::FormatName(const UWorld *World)
{
return World->GetPathName();