Finished implementing the type parser
This commit is contained in:
@@ -1,12 +1,20 @@
|
||||
#include "MCPTypes.h"
|
||||
#include "MCPServer.h"
|
||||
#include "Editor.h"
|
||||
#include "EdGraphSchema_K2.h"
|
||||
#include "Engine/Blueprint.h"
|
||||
#include "UObject/UObjectIterator.h"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Choose Short Name
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void UMCPTypes::ReserveShortName(FName Name)
|
||||
{
|
||||
FString NameStr = Name.ToString();
|
||||
ShortToPath[NameStr.ToLower()] = TEXT("PRIMITIVE");
|
||||
}
|
||||
|
||||
FString UMCPTypes::ChooseShortName(UObject* Obj)
|
||||
{
|
||||
if (!Cast<UScriptStruct>(Obj) && !Cast<UClass>(Obj) && !Cast<UEnum>(Obj))
|
||||
@@ -134,6 +142,7 @@ FString UMCPTypes::TypeToText(const FEdGraphPinType& PinType)
|
||||
return Inner;
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Subsystem lifecycle
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -148,11 +157,23 @@ void UMCPTypes::Initialize(FSubsystemCollectionBase& Collection)
|
||||
Packages.Add(*It);
|
||||
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.
|
||||
ChooseShortNames(FindPackage(nullptr, TEXT("/Script/CoreUObject")));
|
||||
ChooseShortNames(FindPackage(nullptr, TEXT("/Script/Engine")));
|
||||
ChooseShortNames(FindPackage(nullptr, TEXT("/Script/Integration")));
|
||||
|
||||
// Choose short names for everything else.
|
||||
for (UPackage* Pkg : Packages)
|
||||
ChooseShortNames(Pkg);
|
||||
|
||||
@@ -164,3 +185,278 @@ void UMCPTypes::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.
|
||||
//
|
||||
// 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()
|
||||
@@ -34,16 +21,23 @@ public:
|
||||
// Convert a pin type to a type string. Returns empty string on failure.
|
||||
static FString TypeToText(const FEdGraphPinType& PinType);
|
||||
|
||||
// Convert a concise string back to an FEdGraphPinType.
|
||||
// Returns true on success. Prints error via UMCPServer on failure.
|
||||
static bool TextToType(const FString& Text, FEdGraphPinType& OutPinType);
|
||||
// Try to parse a type. If there's a problem, returns an error
|
||||
// message. If all goes well, returns empty string.
|
||||
static FString TryTextToType(const FString& Text, FEdGraphPinType& OutPinType);
|
||||
|
||||
// Same as TextToType, but does not print an error message on failure.
|
||||
static bool TryTextToType(const FString& Text, FEdGraphPinType& OutPinType);
|
||||
// Try to parse a type. If there's a problem, prints an error
|
||||
// via UMCPServer and returns false.
|
||||
static bool TextToType(const FString& Text, FEdGraphPinType& OutPinType);
|
||||
|
||||
private:
|
||||
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
|
||||
// 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
|
||||
@@ -57,7 +51,23 @@ private:
|
||||
TMap<FString, FString> ShortToPath; // e.g. "vector" -> "/Script/CoreUObject.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;
|
||||
int32 Cursor = 0;
|
||||
FString Error;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user