Finished implementing the type parser

This commit is contained in:
2026-03-15 05:30:40 -04:00
parent 6b21fb9674
commit 72139e9be5
2 changed files with 326 additions and 20 deletions

View File

@@ -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;
}