Finished implementing the type parser
This commit is contained in:
@@ -1,12 +1,20 @@
|
|||||||
#include "MCPTypes.h"
|
#include "MCPTypes.h"
|
||||||
|
#include "MCPServer.h"
|
||||||
#include "Editor.h"
|
#include "Editor.h"
|
||||||
#include "EdGraphSchema_K2.h"
|
#include "EdGraphSchema_K2.h"
|
||||||
|
#include "Engine/Blueprint.h"
|
||||||
#include "UObject/UObjectIterator.h"
|
#include "UObject/UObjectIterator.h"
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Choose Short Name
|
// Choose Short Name
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void UMCPTypes::ReserveShortName(FName Name)
|
||||||
|
{
|
||||||
|
FString NameStr = Name.ToString();
|
||||||
|
ShortToPath[NameStr.ToLower()] = TEXT("PRIMITIVE");
|
||||||
|
}
|
||||||
|
|
||||||
FString UMCPTypes::ChooseShortName(UObject* Obj)
|
FString UMCPTypes::ChooseShortName(UObject* Obj)
|
||||||
{
|
{
|
||||||
if (!Cast<UScriptStruct>(Obj) && !Cast<UClass>(Obj) && !Cast<UEnum>(Obj))
|
if (!Cast<UScriptStruct>(Obj) && !Cast<UClass>(Obj) && !Cast<UEnum>(Obj))
|
||||||
@@ -134,6 +142,7 @@ FString UMCPTypes::TypeToText(const FEdGraphPinType& PinType)
|
|||||||
return Inner;
|
return Inner;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Subsystem lifecycle
|
// Subsystem lifecycle
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -148,11 +157,23 @@ void UMCPTypes::Initialize(FSubsystemCollectionBase& Collection)
|
|||||||
Packages.Add(*It);
|
Packages.Add(*It);
|
||||||
Packages.Sort([](const UPackage& A, const UPackage& B) { return A.GetName() < B.GetName(); });
|
Packages.Sort([](const UPackage& A, const UPackage& B) { return A.GetName() < B.GetName(); });
|
||||||
|
|
||||||
|
// Reserve the short names of the primitives.
|
||||||
|
ReserveShortName(UEdGraphSchema_K2::PC_Boolean);
|
||||||
|
ReserveShortName(UEdGraphSchema_K2::PC_Int);
|
||||||
|
ReserveShortName(UEdGraphSchema_K2::PC_Int64);
|
||||||
|
ReserveShortName(UEdGraphSchema_K2::PC_Float);
|
||||||
|
ReserveShortName(UEdGraphSchema_K2::PC_Double);
|
||||||
|
ReserveShortName(UEdGraphSchema_K2::PC_Byte);
|
||||||
|
ReserveShortName(UEdGraphSchema_K2::PC_Name);
|
||||||
|
ReserveShortName(UEdGraphSchema_K2::PC_String);
|
||||||
|
ReserveShortName(UEdGraphSchema_K2::PC_Text);
|
||||||
|
|
||||||
// Scan priority packages first, then everything else in sorted order.
|
// Scan priority packages first, then everything else in sorted order.
|
||||||
ChooseShortNames(FindPackage(nullptr, TEXT("/Script/CoreUObject")));
|
ChooseShortNames(FindPackage(nullptr, TEXT("/Script/CoreUObject")));
|
||||||
ChooseShortNames(FindPackage(nullptr, TEXT("/Script/Engine")));
|
ChooseShortNames(FindPackage(nullptr, TEXT("/Script/Engine")));
|
||||||
ChooseShortNames(FindPackage(nullptr, TEXT("/Script/Integration")));
|
ChooseShortNames(FindPackage(nullptr, TEXT("/Script/Integration")));
|
||||||
|
|
||||||
|
// Choose short names for everything else.
|
||||||
for (UPackage* Pkg : Packages)
|
for (UPackage* Pkg : Packages)
|
||||||
ChooseShortNames(Pkg);
|
ChooseShortNames(Pkg);
|
||||||
|
|
||||||
@@ -164,3 +185,278 @@ void UMCPTypes::Deinitialize()
|
|||||||
Super::Deinitialize();
|
Super::Deinitialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Tokenizer
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void UMCPTypes::Tokenize(const FString& Input)
|
||||||
|
{
|
||||||
|
Tokens.Empty();
|
||||||
|
Cursor = 0;
|
||||||
|
|
||||||
|
int32 i = 0;
|
||||||
|
while (i < Input.Len())
|
||||||
|
{
|
||||||
|
TCHAR Ch = Input[i];
|
||||||
|
|
||||||
|
// Skip whitespace.
|
||||||
|
if (FChar::IsWhitespace(Ch))
|
||||||
|
{
|
||||||
|
++i;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to parse an identifier.
|
||||||
|
int32 Start = i;
|
||||||
|
while (i < Input.Len() && (FChar::IsAlnum(Input[i]) || Input[i] == '_'))
|
||||||
|
++i;
|
||||||
|
if (i > Start)
|
||||||
|
{
|
||||||
|
Tokens.Add(Input.Mid(Start, i - Start));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Anything that's not an identifier or whitespace
|
||||||
|
// gets classified as a single-character token.
|
||||||
|
Tokens.Add(FString(1, &Ch));
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Path to Object Conversion
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
bool UMCPTypes::ResolvePath(const FString &Name, const FString &Path, FEdGraphPinType &OutType)
|
||||||
|
{
|
||||||
|
// Load the object.
|
||||||
|
UObject* Obj = LoadObject<UObject>(nullptr, *Path);
|
||||||
|
if (!Obj)
|
||||||
|
{
|
||||||
|
Error = FString::Printf(TEXT("Failed to load type '%s' at path '%s'"), *Name, *Path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's a blueprint, use its generated class.
|
||||||
|
if (UBlueprint* BP = Cast<UBlueprint>(Obj))
|
||||||
|
{
|
||||||
|
Obj = BP->GeneratedClass;
|
||||||
|
if (!Obj)
|
||||||
|
{
|
||||||
|
Error = FString::Printf(TEXT("Blueprint '%s' has no generated class"), *Name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the category from the object type.
|
||||||
|
if (Cast<UScriptStruct>(Obj))
|
||||||
|
{
|
||||||
|
OutType.PinCategory = UEdGraphSchema_K2::PC_Struct;
|
||||||
|
}
|
||||||
|
else if (UClass* Class = Cast<UClass>(Obj))
|
||||||
|
{
|
||||||
|
if (Class->IsChildOf(UInterface::StaticClass()))
|
||||||
|
OutType.PinCategory = UEdGraphSchema_K2::PC_Interface;
|
||||||
|
else
|
||||||
|
OutType.PinCategory = UEdGraphSchema_K2::PC_Object;
|
||||||
|
}
|
||||||
|
else if (Cast<UEnum>(Obj))
|
||||||
|
{
|
||||||
|
OutType.PinCategory = UEdGraphSchema_K2::PC_Byte;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// This really shouldn't happen.
|
||||||
|
Error = FString::Printf(TEXT("'%s' is not a struct, class, enum, or interface"), *Name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
OutType.PinSubCategoryObject = Obj;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Parsing Types
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
bool UMCPTypes::TokenIs(const TCHAR* Text) const
|
||||||
|
{
|
||||||
|
if (Cursor >= Tokens.Num()) return false;
|
||||||
|
return Tokens[Cursor].Equals(Text, ESearchCase::IgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UMCPTypes::TokenIs(const TCHAR* Text, TCHAR Next) const
|
||||||
|
{
|
||||||
|
if (Cursor >= Tokens.Num() - 1) return false;
|
||||||
|
return (Tokens[Cursor].Equals(Text, ESearchCase::IgnoreCase)) &&
|
||||||
|
(Tokens[Cursor+1].Len() == 1) &&
|
||||||
|
(Tokens[Cursor+1][0] == Next);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UMCPTypes::TokenIs(TCHAR Next) const
|
||||||
|
{
|
||||||
|
if (Cursor >= Tokens.Num()) return false;
|
||||||
|
return (Tokens[Cursor].Len() == 1) &&
|
||||||
|
(Tokens[Cursor][0] == Next);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UMCPTypes::TokenIsID() const
|
||||||
|
{
|
||||||
|
if (Cursor >= Tokens.Num()) return false;
|
||||||
|
return FChar::IsAlnum(Tokens[Cursor][0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UMCPTypes::ParseEOF()
|
||||||
|
{
|
||||||
|
if (Cursor != Tokens.Num())
|
||||||
|
{
|
||||||
|
Error = TEXT("Extra tokens at end of input");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UMCPTypes::ParseChar(TCHAR c)
|
||||||
|
{
|
||||||
|
if (!TokenIs(c))
|
||||||
|
{
|
||||||
|
Error = FString::Printf(TEXT("Expected %c"), c);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Cursor++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UMCPTypes::ParsePlainIdentifier(FEdGraphPinType& OutType)
|
||||||
|
{
|
||||||
|
if (!TokenIsID())
|
||||||
|
{
|
||||||
|
Error = TEXT("Expected Identifier");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
FString Name = Tokens[Cursor++];
|
||||||
|
FString *Path = ShortToPath.Find(Name.ToLower());
|
||||||
|
if (Path == nullptr)
|
||||||
|
{
|
||||||
|
Error = TEXT("Unrecognized Type");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*Path == TEXT("PRIMITIVE"))
|
||||||
|
{
|
||||||
|
OutType.PinCategory = FName(*Name);
|
||||||
|
if ((OutType.PinCategory == UEdGraphSchema_K2::PC_Double) ||
|
||||||
|
(OutType.PinCategory == UEdGraphSchema_K2::PC_Float))
|
||||||
|
{
|
||||||
|
OutType.PinSubCategory = OutType.PinCategory;
|
||||||
|
OutType.PinCategory = UEdGraphSchema_K2::PC_Real;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return ResolvePath(Name, *Path, OutType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UMCPTypes::ParseWrapped(FName Wrapper, FEdGraphPinType& OutType)
|
||||||
|
{
|
||||||
|
Cursor++;
|
||||||
|
if (!ParseChar('<')) 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;
|
||||||
|
OutType.PinCategory = Wrapper;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UMCPTypes::ParseMaybeWrapped(FEdGraphPinType& OutType)
|
||||||
|
{
|
||||||
|
if (TokenIs(TEXT("Soft"), '<'))
|
||||||
|
{
|
||||||
|
return ParseWrapped(UEdGraphSchema_K2::PC_SoftObject, OutType);
|
||||||
|
}
|
||||||
|
else if (TokenIs(TEXT("Class"), '<'))
|
||||||
|
{
|
||||||
|
return ParseWrapped(UEdGraphSchema_K2::PC_Class, OutType);
|
||||||
|
}
|
||||||
|
else if (TokenIs(TEXT("SoftClass"), '<'))
|
||||||
|
{
|
||||||
|
return ParseWrapped(UEdGraphSchema_K2::PC_SoftClass, OutType);
|
||||||
|
}
|
||||||
|
else return ParsePlainIdentifier(OutType);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UMCPTypes::ParseArrayOrSet(FEdGraphPinType& OutType)
|
||||||
|
{
|
||||||
|
Cursor++;
|
||||||
|
if (!ParseChar('<')) return false;
|
||||||
|
if (!ParseMaybeWrapped(OutType)) return false;
|
||||||
|
if (!ParseChar('>')) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UMCPTypes::ParseMap(FEdGraphPinType& OutType)
|
||||||
|
{
|
||||||
|
Cursor++;
|
||||||
|
if (!ParseChar('<')) return false;
|
||||||
|
if (!ParsePlainIdentifier(OutType)) return false;
|
||||||
|
if (!ParseChar(',')) 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;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UMCPTypes::ParseType(FEdGraphPinType& OutType)
|
||||||
|
{
|
||||||
|
if (TokenIs(TEXT("Array"), '<'))
|
||||||
|
{
|
||||||
|
OutType.ContainerType = EPinContainerType::Array;
|
||||||
|
if (!ParseArrayOrSet(OutType)) return false;
|
||||||
|
}
|
||||||
|
else if (TokenIs(TEXT("Set"), '<'))
|
||||||
|
{
|
||||||
|
OutType.ContainerType = EPinContainerType::Set;
|
||||||
|
if (!ParseArrayOrSet(OutType)) return false;
|
||||||
|
}
|
||||||
|
else if (TokenIs(TEXT("Map"), '<'))
|
||||||
|
{
|
||||||
|
OutType.ContainerType = EPinContainerType::Map;
|
||||||
|
if (!ParseMap(OutType)) return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!ParseMaybeWrapped(OutType)) return false;
|
||||||
|
}
|
||||||
|
if (!ParseEOF()) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
FString UMCPTypes::TryTextToType(const FString& Text, FEdGraphPinType& OutPinType)
|
||||||
|
{
|
||||||
|
UMCPTypes* Types = GEditor->GetEditorSubsystem<UMCPTypes>();
|
||||||
|
check(Types);
|
||||||
|
Types->Error.Empty();
|
||||||
|
Types->Tokenize(Text);
|
||||||
|
OutPinType = FEdGraphPinType();
|
||||||
|
if (!Types->ParseType(OutPinType)) return Types->Error;
|
||||||
|
return FString();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UMCPTypes::TextToType(const FString& Text, FEdGraphPinType& OutPinType)
|
||||||
|
{
|
||||||
|
FString Error = TryTextToType(Text, OutPinType);
|
||||||
|
if (Error.IsEmpty()) return true;
|
||||||
|
UMCPServer::Print(Error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,19 +7,6 @@
|
|||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// UMCPTypes — converts between FEdGraphPinType and a concise C++-like string.
|
// UMCPTypes — converts between FEdGraphPinType and a concise C++-like string.
|
||||||
//
|
|
||||||
// The text format mirrors GetCPPType where possible:
|
|
||||||
// bool, uint8, int32, int64, float, double, FName, FString, FText
|
|
||||||
// FVector, FRotator, FTransform, ... (structs)
|
|
||||||
// EBlendMode, ... (enums)
|
|
||||||
// AActor*, UStaticMesh*, ... (object references)
|
|
||||||
// TSubclassOf<AActor> (class references)
|
|
||||||
// TSoftObjectPtr<AActor> (soft object references)
|
|
||||||
// TSoftClassPtr<AActor> (soft class references)
|
|
||||||
// TScriptInterface<IMyInterface> (interfaces)
|
|
||||||
// TArray<float>, TSet<FName>, TMap<FString, int32> (containers)
|
|
||||||
//
|
|
||||||
// TypeToText and TextToType are inverses of each other.
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
UCLASS()
|
UCLASS()
|
||||||
@@ -34,16 +21,23 @@ public:
|
|||||||
// Convert a pin type to a type string. Returns empty string on failure.
|
// Convert a pin type to a type string. Returns empty string on failure.
|
||||||
static FString TypeToText(const FEdGraphPinType& PinType);
|
static FString TypeToText(const FEdGraphPinType& PinType);
|
||||||
|
|
||||||
// Convert a concise string back to an FEdGraphPinType.
|
// Try to parse a type. If there's a problem, returns an error
|
||||||
// Returns true on success. Prints error via UMCPServer on failure.
|
// message. If all goes well, returns empty string.
|
||||||
static bool TextToType(const FString& Text, FEdGraphPinType& OutPinType);
|
static FString TryTextToType(const FString& Text, FEdGraphPinType& OutPinType);
|
||||||
|
|
||||||
// Same as TextToType, but does not print an error message on failure.
|
// Try to parse a type. If there's a problem, prints an error
|
||||||
static bool TryTextToType(const FString& Text, FEdGraphPinType& OutPinType);
|
// via UMCPServer and returns false.
|
||||||
|
static bool TextToType(const FString& Text, FEdGraphPinType& OutPinType);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FString TypeToTextInner(FName Category, FName SubCategory, UObject* SubCategoryObject);
|
FString TypeToTextInner(FName Category, FName SubCategory, UObject* SubCategoryObject);
|
||||||
|
|
||||||
|
bool ResolvePath(const FString &Name, const FString &Path, FEdGraphPinType &OutType);
|
||||||
|
|
||||||
|
// Reserve the short name for a primitive type.
|
||||||
|
// The value stored in the map is just "PRIMITIVE".
|
||||||
|
void ReserveShortName(FName Name);
|
||||||
|
|
||||||
// Chooses a short name for the specified type, making sure not to
|
// Chooses a short name for the specified type, making sure not to
|
||||||
// choose the same name for two classes. If the object already has
|
// choose the same name for two classes. If the object already has
|
||||||
// a short name, return it. If it's not a class, struct, enum, or
|
// a short name, return it. If it's not a class, struct, enum, or
|
||||||
@@ -57,7 +51,23 @@ private:
|
|||||||
TMap<FString, FString> ShortToPath; // e.g. "vector" -> "/Script/CoreUObject.Vector"
|
TMap<FString, FString> ShortToPath; // e.g. "vector" -> "/Script/CoreUObject.Vector"
|
||||||
TMap<FString, FString> PathToShort; // e.g. "/Script/CoreUObject.Vector" -> "Vector"
|
TMap<FString, FString> PathToShort; // e.g. "/Script/CoreUObject.Vector" -> "Vector"
|
||||||
|
|
||||||
// Parser state (used by TextToType)
|
// Tokenizer and parser (used by TextToType)
|
||||||
|
void Tokenize(const FString& Input);
|
||||||
|
bool TokenIs(const TCHAR* Text) const;
|
||||||
|
bool TokenIs(const TCHAR* Text, TCHAR ch) const;
|
||||||
|
bool TokenIs(TCHAR Ch) const;
|
||||||
|
bool TokenIsID() const;
|
||||||
|
|
||||||
|
bool ParseEOF();
|
||||||
|
bool ParseChar(TCHAR c);
|
||||||
|
bool ParsePlainIdentifier(FEdGraphPinType& OutType);
|
||||||
|
bool ParseWrapped(FName Wrapper, FEdGraphPinType& OutType);
|
||||||
|
bool ParseMaybeWrapped(FEdGraphPinType& OutType);
|
||||||
|
bool ParseArrayOrSet(FEdGraphPinType& OutType);
|
||||||
|
bool ParseMap(FEdGraphPinType& OutType);
|
||||||
|
bool ParseType(FEdGraphPinType& OutType);
|
||||||
|
|
||||||
TArray<FString> Tokens;
|
TArray<FString> Tokens;
|
||||||
int32 Cursor = 0;
|
int32 Cursor = 0;
|
||||||
|
FString Error;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user