diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Test_TypeToText.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Test_TypeToText.h new file mode 100644 index 00000000..0ec5ebdc --- /dev/null +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Test_TypeToText.h @@ -0,0 +1,63 @@ +#pragma once + +#include "CoreMinimal.h" +#include "WingServer.h" +#include "WingHandler.h" +#include "WingTypes.h" +#include "Test_TypeToText.generated.h" + + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- + +UCLASS() +class UWing_Test_TypeToText : public UObject, public IWingHandler +{ + GENERATED_BODY() + +public: + UPROPERTY(meta=(Description="The type name to parse, e.g. 'Array'")) + FString Input; + + virtual FString GetDescription() const override + { + return TEXT("Test the type parser by parsing a type name and dumping the resulting FEdGraphPinType."); + } + + virtual void Handle() override + { + FEdGraphPinType PinType; + UWingTypes::Requirements Require; + Require.BlueprintType = false; + Require.Blueprintable = false; + Require.AllowContainer = true; + + bool OK = UWingTypes::TextToType(Input, PinType, Require); + + auto& Out = UWingServer::GetPrintBuffer(); + Out.Appendf(TEXT("ParseResult: %s\n"), OK ? TEXT("OK") : TEXT("FAILED")); + Out.Appendf(TEXT("PinCategory: %s\n"), *PinType.PinCategory.ToString()); + Out.Appendf(TEXT("PinSubCategory: %s\n"), *PinType.PinSubCategory.ToString()); + Out.Appendf(TEXT("PinSubCategoryObject: %s\n"), + PinType.PinSubCategoryObject.IsValid() + ? *PinType.PinSubCategoryObject->GetPathName() + : TEXT("(none)")); + Out.Appendf(TEXT("ContainerType: %d\n"), (int32)PinType.ContainerType); + if (PinType.IsMap()) + { + Out.Appendf(TEXT("ValueTerminalCategory: %s\n"), *PinType.PinValueType.TerminalCategory.ToString()); + Out.Appendf(TEXT("ValueTerminalSubCategory: %s\n"), *PinType.PinValueType.TerminalSubCategory.ToString()); + Out.Appendf(TEXT("ValueTerminalSubCategoryObject: %s\n"), + PinType.PinValueType.TerminalSubCategoryObject.IsValid() + ? *PinType.PinValueType.TerminalSubCategoryObject->GetPathName() + : TEXT("(none)")); + } + + if (OK) + { + FString RoundTrip = UWingTypes::TypeToText(PinType); + Out.Appendf(TEXT("TypeToText: %s\n"), RoundTrip.IsEmpty() ? TEXT("(empty)") : *RoundTrip); + } + } +}; diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingTypes.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingTypes.cpp index dcc2ab5b..1a71ec06 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingTypes.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingTypes.cpp @@ -421,195 +421,142 @@ FString UWingTypes::TypeToTextOrDie(const UObject* Obj) } // --------------------------------------------------------------------------- -// Tokenizer, Parser, and Resolve Short Name +// Parser and Resolve Short Name // --------------------------------------------------------------------------- -void UWingTypes::PrintParseError(const TCHAR* Message) +static const FName NAME_TypeArray(TEXT("Array")); +static const FName NAME_TypeSet(TEXT("Set")); +static const FName NAME_TypeMap(TEXT("Map")); +static const FName NAME_TypeSoft(TEXT("Soft")); +static const FName NAME_TypeClass(TEXT("Class")); +static const FName NAME_TypeSoftClass(TEXT("SoftClass")); + +void UWingTypes::PrintParseError(WingTokenizer& Tok, const TCHAR* Message) { - UWingServer::Printf(TEXT("ERROR parsing type '%s' — %s\n"), *ParseInput, Message); + UWingServer::Printf(TEXT("ERROR parsing type '%s' — %s\n"), *Tok.GetInput(), Message); UWingServer::SuggestManual(WingManual::Section::Types); } -void UWingTypes::Tokenize(const FString& Input) +bool UWingTypes::ParseEOF(WingTokenizer& Tok) { - Tokens.Empty(); - Cursor = 0; - - int32 i = 0; - while (i < Input.Len()) + if (Tok.NextType() != 0) { - 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; - } -} - -bool UWingTypes::TokenIs(const TCHAR* Text) const -{ - if (Cursor >= Tokens.Num()) return false; - return Tokens[Cursor].Equals(Text, ESearchCase::IgnoreCase); -} - -bool UWingTypes::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 UWingTypes::TokenIs(TCHAR Next) const -{ - if (Cursor >= Tokens.Num()) return false; - return (Tokens[Cursor].Len() == 1) && - (Tokens[Cursor][0] == Next); -} - -bool UWingTypes::TokenIsID() const -{ - if (Cursor >= Tokens.Num()) return false; - return FChar::IsAlnum(Tokens[Cursor][0]); -} - -bool UWingTypes::ParseEOF() -{ - if (Cursor != Tokens.Num()) - { - PrintParseError(TEXT("extra tokens at end of input")); + PrintParseError(Tok, TEXT("extra tokens at end of input")); return false; } return true; } -bool UWingTypes::ParseChar(TCHAR c) +bool UWingTypes::ParseChar(WingTokenizer& Tok, TCHAR c) { - if (!TokenIs(c)) + if (!Tok.TokenIs(c)) { - PrintParseError(*FString::Printf(TEXT("expected '%c'"), c)); + PrintParseError(Tok, *FString::Printf(TEXT("expected '%c'"), c)); return false; } - Cursor++; + Tok.Advance(); return true; } -bool UWingTypes::ParsePlainIdentifier(FEdGraphPinType& OutType) +bool UWingTypes::ParsePlainIdentifier(WingTokenizer& Tok, FEdGraphPinType& OutType) { - if (!TokenIsID()) + if (!Tok.TokenIs(Tok.Identifier)) { - PrintParseError(TEXT("expected identifier")); + PrintParseError(Tok, TEXT("expected identifier")); return false; } - FString Name = Tokens[Cursor++]; - return ResolveShortName(Name, OutType); + FString Name = Tok.NextName().ToString(); + Tok.Advance(); + return ResolveShortName(Tok, Name, OutType); } -bool UWingTypes::ParseWrapped(FName Wrapper, FEdGraphPinType& OutType) +bool UWingTypes::ParseWrapped(WingTokenizer& Tok, FName Wrapper, FEdGraphPinType& OutType) { - Cursor++; - if (!ParseChar('<')) return false; - if (!ParsePlainIdentifier(OutType)) return false; + Tok.Advance(); + if (!ParseChar(Tok, '<')) return false; + if (!ParsePlainIdentifier(Tok, OutType)) return false; if (OutType.PinCategory != UEdGraphSchema_K2::PC_Object) { - PrintParseError(*FString::Printf(TEXT("'%s' is not an object type"), *OutType.PinSubCategoryObject->GetName())); + PrintParseError(Tok, *FString::Printf(TEXT("'%s' is not an object type"), *OutType.PinSubCategoryObject->GetName())); return false; } - if (!ParseChar('>')) return false; + if (!ParseChar(Tok, '>')) return false; OutType.PinCategory = Wrapper; return true; } -bool UWingTypes::ParseMaybeWrapped(FEdGraphPinType& OutType) +bool UWingTypes::ParseMaybeWrapped(WingTokenizer& Tok, FEdGraphPinType& OutType) { - if (TokenIs(TEXT("Soft"), '<')) + if (Tok.TokenIs(NAME_TypeSoft, '<')) { - return ParseWrapped(UEdGraphSchema_K2::PC_SoftObject, OutType); + return ParseWrapped(Tok, UEdGraphSchema_K2::PC_SoftObject, OutType); } - else if (TokenIs(TEXT("Class"), '<')) + else if (Tok.TokenIs(NAME_TypeClass, '<')) { - return ParseWrapped(UEdGraphSchema_K2::PC_Class, OutType); + return ParseWrapped(Tok, UEdGraphSchema_K2::PC_Class, OutType); } - else if (TokenIs(TEXT("SoftClass"), '<')) + else if (Tok.TokenIs(NAME_TypeSoftClass, '<')) { - return ParseWrapped(UEdGraphSchema_K2::PC_SoftClass, OutType); + return ParseWrapped(Tok, UEdGraphSchema_K2::PC_SoftClass, OutType); } - else return ParsePlainIdentifier(OutType); + else return ParsePlainIdentifier(Tok, OutType); } -bool UWingTypes::ParseArrayOrSet(FEdGraphPinType& OutType) +bool UWingTypes::ParseArrayOrSet(WingTokenizer& Tok, FEdGraphPinType& OutType) { - Cursor++; - if (!ParseChar('<')) return false; - if (!ParseMaybeWrapped(OutType)) return false; - if (!ParseChar('>')) return false; + Tok.Advance(); + if (!ParseChar(Tok, '<')) return false; + if (!ParseMaybeWrapped(Tok, OutType)) return false; + if (!ParseChar(Tok, '>')) return false; return true; } -bool UWingTypes::ParseMap(FEdGraphPinType& OutType) +bool UWingTypes::ParseMap(WingTokenizer& Tok, FEdGraphPinType& OutType) { - Cursor++; - if (!ParseChar('<')) return false; - if (!ParsePlainIdentifier(OutType)) return false; - if (!ParseChar(',')) return false; + Tok.Advance(); + if (!ParseChar(Tok, '<')) return false; + if (!ParsePlainIdentifier(Tok, OutType)) return false; + if (!ParseChar(Tok, ',')) return false; FEdGraphPinType ValueType; - if (!ParseMaybeWrapped(ValueType)) return false; + if (!ParseMaybeWrapped(Tok, ValueType)) return false; OutType.PinValueType.TerminalCategory = ValueType.PinCategory; OutType.PinValueType.TerminalSubCategory = ValueType.PinSubCategory; OutType.PinValueType.TerminalSubCategoryObject = ValueType.PinSubCategoryObject; - if (!ParseChar('>')) return false; + if (!ParseChar(Tok, '>')) return false; return true; } -bool UWingTypes::ParseType(FEdGraphPinType& OutType) +bool UWingTypes::ParseType(WingTokenizer& Tok, FEdGraphPinType& OutType) { - if (TokenIs(TEXT("Array"), '<')) + if (Tok.TokenIs(NAME_TypeArray, '<')) { OutType.ContainerType = EPinContainerType::Array; - if (!ParseArrayOrSet(OutType)) return false; - } - else if (TokenIs(TEXT("Set"), '<')) + if (!ParseArrayOrSet(Tok, OutType)) return false; + } + else if (Tok.TokenIs(NAME_TypeSet, '<')) { OutType.ContainerType = EPinContainerType::Set; - if (!ParseArrayOrSet(OutType)) return false; + if (!ParseArrayOrSet(Tok, OutType)) return false; } - else if (TokenIs(TEXT("Map"), '<')) + else if (Tok.TokenIs(NAME_TypeMap, '<')) { OutType.ContainerType = EPinContainerType::Map; - if (!ParseMap(OutType)) return false; + if (!ParseMap(Tok, OutType)) return false; } else { - if (!ParseMaybeWrapped(OutType)) return false; + if (!ParseMaybeWrapped(Tok, OutType)) return false; } - if (!ParseEOF()) return false; + if (!ParseEOF(Tok)) return false; return true; } -bool UWingTypes::ResolveShortName(const FString &Name, FEdGraphPinType &OutType) +bool UWingTypes::ResolveShortName(WingTokenizer& Tok, const FString &Name, FEdGraphPinType &OutType) { Info* TypeInfo = ShortToInfo.Find(Name.ToLower()); if (!TypeInfo) { - PrintParseError(*FString::Printf(TEXT("unrecognized type '%s'"), *Name)); + PrintParseError(Tok, *FString::Printf(TEXT("unrecognized type '%s'"), *Name)); return false; } @@ -625,7 +572,7 @@ bool UWingTypes::ResolveShortName(const FString &Name, FEdGraphPinType &OutType) UObject* Obj = LoadObject(nullptr, *TypeInfo->PinSubCategoryObject); if (!Obj) { - PrintParseError(*FString::Printf(TEXT("failed to load type '%s' at path '%s'"), *Name, *TypeInfo->PinSubCategoryObject)); + PrintParseError(Tok, *FString::Printf(TEXT("failed to load type '%s' at path '%s'"), *Name, *TypeInfo->PinSubCategoryObject)); return false; } @@ -676,10 +623,9 @@ bool UWingTypes::TextToType(const FString& Text, FEdGraphPinType& OutPinType, co UWingTypes* Types = GEditor->GetEditorSubsystem(); check(Types); - Types->ParseInput = Text; - Types->Tokenize(Text); + WingTokenizer Tok(Text); OutPinType = FEdGraphPinType(); - if (!Types->ParseType(OutPinType)) + if (!Types->ParseType(Tok, OutPinType)) { OutPinType = FEdGraphPinType(); return false; } diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingTokenizer.h b/Plugins/UEWingman/Source/UEWingman/Public/WingTokenizer.h index 4fe0cd32..2ac4b7ed 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingTokenizer.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingTokenizer.h @@ -123,10 +123,13 @@ struct WingTokenizer // Advance the cursor. Don't move past the two sentinels. void Advance() { int I = Next-Tokens.GetData(); if (I + 2 < Tokens.Num()) Next++; } + // Get the original input string. + const FString& GetInput() const { return Input; } + // Tokenize a line of input. The tokens are stored in // the token array, and the cursor is positioned on the first // token. If there's an error, the error is stored in the - // error field and the cursor points to the empty token. + // error field and the cursor points to the empty token. WingTokenizer(const FString& Input); // Print all tokens into a string builder for debugging. diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingTypes.h b/Plugins/UEWingman/Source/UEWingman/Public/WingTypes.h index 723364f3..970b8719 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingTypes.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingTypes.h @@ -5,6 +5,8 @@ #include "EdGraph/EdGraphPin.h" #include "WingTypes.generated.h" +struct WingTokenizer; + // --------------------------------------------------------------------------- // UWingTypes — converts between FEdGraphPinType and a concise C++-like string. // --------------------------------------------------------------------------- @@ -112,25 +114,20 @@ public: private: // --------------------------------------------------------------------------- - // Tokenizer, Parser, and ResolveShortName + // Parser and ResolveShortName // --------------------------------------------------------------------------- - 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; + static void PrintParseError(WingTokenizer& Tok, const TCHAR* Message); + static bool ParseEOF(WingTokenizer& Tok); + static bool ParseChar(WingTokenizer& Tok, TCHAR c); + bool ParsePlainIdentifier(WingTokenizer& Tok, FEdGraphPinType& OutType); + bool ParseWrapped(WingTokenizer& Tok, FName Wrapper, FEdGraphPinType& OutType); + bool ParseMaybeWrapped(WingTokenizer& Tok, FEdGraphPinType& OutType); + bool ParseArrayOrSet(WingTokenizer& Tok, FEdGraphPinType& OutType); + bool ParseMap(WingTokenizer& Tok, FEdGraphPinType& OutType); + bool ParseType(WingTokenizer& Tok, FEdGraphPinType& OutType); - 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); - - bool ResolveShortName(const FString &Name, FEdGraphPinType &OutType); + bool ResolveShortName(WingTokenizer& Tok, const FString &Name, FEdGraphPinType &OutType); public: // --------------------------------------------------------------------------- @@ -180,9 +177,4 @@ private: // Given a class path, return the short name, e.g. "/Script/CoreUObject.Vector" -> "Vector" TMap PathToShort; - // These fields are only used during the parsing of a type. - TArray Tokens; - int32 Cursor = 0; - FString ParseInput; - void PrintParseError(const TCHAR* Message); };