WingTypes now uses the new tokenizer.

This commit is contained in:
2026-03-30 05:34:17 -04:00
parent a2221bca94
commit 386455b1d0
4 changed files with 144 additions and 140 deletions

View File

@@ -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<Vector>'"))
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);
}
}
};

View File

@@ -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;
if (!ParseArrayOrSet(Tok, OutType)) return false;
}
else if (TokenIs(TEXT("Set"), '<'))
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<UObject>(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<UWingTypes>();
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;
}

View File

@@ -123,6 +123,9 @@ 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

View File

@@ -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<FString, FString> PathToShort;
// These fields are only used during the parsing of a type.
TArray<FString> Tokens;
int32 Cursor = 0;
FString ParseInput;
void PrintParseError(const TCHAR* Message);
};