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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user