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); UWingServer::SuggestManual(WingManual::Section::Types);
} }
void UWingTypes::Tokenize(const FString& Input) bool UWingTypes::ParseEOF(WingTokenizer& Tok)
{ {
Tokens.Empty(); if (Tok.NextType() != 0)
Cursor = 0;
int32 i = 0;
while (i < Input.Len())
{ {
TCHAR Ch = Input[i]; PrintParseError(Tok, TEXT("extra tokens at end of input"));
// 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"));
return false; return false;
} }
return true; 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; return false;
} }
Cursor++; Tok.Advance();
return true; 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; return false;
} }
FString Name = Tokens[Cursor++]; FString Name = Tok.NextName().ToString();
return ResolveShortName(Name, OutType); Tok.Advance();
return ResolveShortName(Tok, Name, OutType);
} }
bool UWingTypes::ParseWrapped(FName Wrapper, FEdGraphPinType& OutType) bool UWingTypes::ParseWrapped(WingTokenizer& Tok, FName Wrapper, FEdGraphPinType& OutType)
{ {
Cursor++; Tok.Advance();
if (!ParseChar('<')) return false; if (!ParseChar(Tok, '<')) return false;
if (!ParsePlainIdentifier(OutType)) return false; if (!ParsePlainIdentifier(Tok, OutType)) return false;
if (OutType.PinCategory != UEdGraphSchema_K2::PC_Object) 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; return false;
} }
if (!ParseChar('>')) return false; if (!ParseChar(Tok, '>')) return false;
OutType.PinCategory = Wrapper; OutType.PinCategory = Wrapper;
return true; 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++; Tok.Advance();
if (!ParseChar('<')) return false; if (!ParseChar(Tok, '<')) return false;
if (!ParseMaybeWrapped(OutType)) return false; if (!ParseMaybeWrapped(Tok, OutType)) return false;
if (!ParseChar('>')) return false; if (!ParseChar(Tok, '>')) return false;
return true; return true;
} }
bool UWingTypes::ParseMap(FEdGraphPinType& OutType) bool UWingTypes::ParseMap(WingTokenizer& Tok, FEdGraphPinType& OutType)
{ {
Cursor++; Tok.Advance();
if (!ParseChar('<')) return false; if (!ParseChar(Tok, '<')) return false;
if (!ParsePlainIdentifier(OutType)) return false; if (!ParsePlainIdentifier(Tok, OutType)) return false;
if (!ParseChar(',')) return false; if (!ParseChar(Tok, ',')) return false;
FEdGraphPinType ValueType; FEdGraphPinType ValueType;
if (!ParseMaybeWrapped(ValueType)) return false; if (!ParseMaybeWrapped(Tok, ValueType)) return false;
OutType.PinValueType.TerminalCategory = ValueType.PinCategory; OutType.PinValueType.TerminalCategory = ValueType.PinCategory;
OutType.PinValueType.TerminalSubCategory = ValueType.PinSubCategory; OutType.PinValueType.TerminalSubCategory = ValueType.PinSubCategory;
OutType.PinValueType.TerminalSubCategoryObject = ValueType.PinSubCategoryObject; OutType.PinValueType.TerminalSubCategoryObject = ValueType.PinSubCategoryObject;
if (!ParseChar('>')) return false; if (!ParseChar(Tok, '>')) return false;
return true; 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; 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; 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; OutType.ContainerType = EPinContainerType::Map;
if (!ParseMap(OutType)) return false; if (!ParseMap(Tok, OutType)) return false;
} }
else else
{ {
if (!ParseMaybeWrapped(OutType)) return false; if (!ParseMaybeWrapped(Tok, OutType)) return false;
} }
if (!ParseEOF()) return false; if (!ParseEOF(Tok)) return false;
return true; 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()); Info* TypeInfo = ShortToInfo.Find(Name.ToLower());
if (!TypeInfo) if (!TypeInfo)
{ {
PrintParseError(*FString::Printf(TEXT("unrecognized type '%s'"), *Name)); PrintParseError(Tok, *FString::Printf(TEXT("unrecognized type '%s'"), *Name));
return false; return false;
} }
@@ -625,7 +572,7 @@ bool UWingTypes::ResolveShortName(const FString &Name, FEdGraphPinType &OutType)
UObject* Obj = LoadObject<UObject>(nullptr, *TypeInfo->PinSubCategoryObject); UObject* Obj = LoadObject<UObject>(nullptr, *TypeInfo->PinSubCategoryObject);
if (!Obj) 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; return false;
} }
@@ -676,10 +623,9 @@ bool UWingTypes::TextToType(const FString& Text, FEdGraphPinType& OutPinType, co
UWingTypes* Types = GEditor->GetEditorSubsystem<UWingTypes>(); UWingTypes* Types = GEditor->GetEditorSubsystem<UWingTypes>();
check(Types); check(Types);
Types->ParseInput = Text; WingTokenizer Tok(Text);
Types->Tokenize(Text);
OutPinType = FEdGraphPinType(); OutPinType = FEdGraphPinType();
if (!Types->ParseType(OutPinType)) if (!Types->ParseType(Tok, OutPinType))
{ {
OutPinType = FEdGraphPinType(); return false; OutPinType = FEdGraphPinType(); return false;
} }

View File

@@ -123,6 +123,9 @@ struct WingTokenizer
// Advance the cursor. Don't move past the two sentinels. // Advance the cursor. Don't move past the two sentinels.
void Advance() { int I = Next-Tokens.GetData(); if (I + 2 < Tokens.Num()) Next++; } 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 // Tokenize a line of input. The tokens are stored in
// the token array, and the cursor is positioned on the first // the token array, and the cursor is positioned on the first
// token. If there's an error, the error is stored in the // token. If there's an error, the error is stored in the

View File

@@ -5,6 +5,8 @@
#include "EdGraph/EdGraphPin.h" #include "EdGraph/EdGraphPin.h"
#include "WingTypes.generated.h" #include "WingTypes.generated.h"
struct WingTokenizer;
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// UWingTypes — converts between FEdGraphPinType and a concise C++-like string. // UWingTypes — converts between FEdGraphPinType and a concise C++-like string.
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -112,25 +114,20 @@ public:
private: private:
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Tokenizer, Parser, and ResolveShortName // Parser and ResolveShortName
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
void Tokenize(const FString& Input); static void PrintParseError(WingTokenizer& Tok, const TCHAR* Message);
bool TokenIs(const TCHAR* Text) const; static bool ParseEOF(WingTokenizer& Tok);
bool TokenIs(const TCHAR* Text, TCHAR ch) const; static bool ParseChar(WingTokenizer& Tok, TCHAR c);
bool TokenIs(TCHAR Ch) const; bool ParsePlainIdentifier(WingTokenizer& Tok, FEdGraphPinType& OutType);
bool TokenIsID() const; 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 ResolveShortName(WingTokenizer& Tok, const FString &Name, FEdGraphPinType &OutType);
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);
public: public:
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -180,9 +177,4 @@ private:
// Given a class path, return the short name, e.g. "/Script/CoreUObject.Vector" -> "Vector" // Given a class path, return the short name, e.g. "/Script/CoreUObject.Vector" -> "Vector"
TMap<FString, FString> PathToShort; 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);
}; };