From 0f7bfebb71a15d417a97e6e049dc52f96c3c8b4d Mon Sep 17 00:00:00 2001 From: jyelon Date: Thu, 26 Mar 2026 17:07:58 -0400 Subject: [PATCH] More work on automatic self-documentation --- .../UEWingman/Handlers/GraphNode_SetArgs.h | 9 ++-- .../UEWingman/Private/WingFunctionArgs.cpp | 17 +++++--- .../Source/UEWingman/Private/WingManual.cpp | 42 +++++++++---------- .../Source/UEWingman/Private/WingTypes.cpp | 33 ++++++++++----- .../Source/UEWingman/Public/WingManual.h | 2 +- .../Source/UEWingman/Public/WingTypes.h | 3 +- 6 files changed, 64 insertions(+), 42 deletions(-) diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/GraphNode_SetArgs.h b/Plugins/UEWingman/Source/UEWingman/Handlers/GraphNode_SetArgs.h index 49371642..9ae26bf9 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/GraphNode_SetArgs.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/GraphNode_SetArgs.h @@ -14,18 +14,18 @@ class UWing_GraphNode_SetArgs : public UObject, public IWingHandler GENERATED_BODY() public: - UPROPERTY(meta=(Description="Path to a graph node (function entry, function result, custom event, or tunnel)")) + UPROPERTY(meta=(Description="Path to a graph node (FunctionEntry, FunctionResult, CustomEvent, or Tunnel)")) FString Node; - UPROPERTY(meta=(Description="Args e.g. 'int x,float y'")) + UPROPERTY(meta=(Description="Parameter list, such as 'int x,float y'")) FString Args; - UPROPERTY(meta=(Optional, Description="Also rename the node (e.g. custom event name)")) + UPROPERTY(meta=(Optional, Description="Also rename the node (which renames a Function or Custom Event)")) FString Rename; virtual FString GetDescription() const override { - return TEXT("Set the user-defined pins (arguments or return values) on a function entry, result, custom event, or tunnel node."); + return TEXT("Set the parameter list of a FunctionEntry, FunctionResult, CustomEvent, or Tunnel node."); } virtual void Handle() override @@ -37,6 +37,7 @@ public: if (!WingFunctionArgs::HasArgs(NodeObj)) { UWingServer::Printf(TEXT("ERROR: Node does not support editable args\n")); + UWingServer::SuggestManual(WingManual::Section::HandlerHelp); return; } diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingFunctionArgs.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingFunctionArgs.cpp index 5ec51a78..5044a949 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingFunctionArgs.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingFunctionArgs.cpp @@ -55,7 +55,7 @@ bool WingFunctionArgs::ParseArgs(const FString& Args, TArray& OutArg FString TypeStr, NameStr; if (!Token.Split(TEXT(" "), &TypeStr, &NameStr)) { - UWingServer::Printf(TEXT("ERROR: Expected 'type name' but got '%s'\n"), *Token); + UWingServer::Printf(TEXT("ERROR: Malformed parameter list near '%s'\n"), *Token); return false; } @@ -64,7 +64,7 @@ bool WingFunctionArgs::ParseArgs(const FString& Args, TArray& OutArg if (TypeStr.IsEmpty() || NameStr.IsEmpty()) { - UWingServer::Printf(TEXT("ERROR: Expected 'type name' but got '%s'\n"), *Token); + UWingServer::Printf(TEXT("ERROR: Malformed parameter list near '%s'\n"), *Token); return false; } @@ -85,13 +85,18 @@ bool WingFunctionArgs::SetArgs(UEdGraphNode* Node, const FString& Args) UK2Node_EditablePinBase* Editable = Cast(Node); if (!Editable || !Editable->IsEditable()) { - UWingServer::Printf(TEXT("ERROR: Node does not support editable pins\n")); + UWingServer::Printf(TEXT("ERROR: Node does not contain an editable parameter list\n")); return false; } // Parse the args string. TArray NewArgs; - if (!ParseArgs(Args, NewArgs)) return false; + if (!ParseArgs(Args, NewArgs)) + { + UWingServer::SuggestManual(WingManual::Section::ParameterLists); + UWingServer::SuggestManual(WingManual::Section::Types); + return false; + } EEdGraphPinDirection Direction = GetPinDirection(Editable); @@ -117,7 +122,9 @@ bool WingFunctionArgs::CheckArgs(const FString &Args) TArray NewArgs; if (!ParseArgs(Args, NewArgs)) { - UWingServer::Printf(TEXT("Invalid function arguments: %s\n"), *Args); + UWingServer::Printf(TEXT("ERROR: Invalid parameter list: %s\n"), *Args); + UWingServer::SuggestManual(WingManual::Section::ParameterLists); + UWingServer::SuggestManual(WingManual::Section::Types); return false; } return true; diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingManual.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingManual.cpp index 1e6a2339..f2f32932 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingManual.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingManual.cpp @@ -8,7 +8,7 @@ TSet WingManual::AllSections() return { Section::Paths, Section::Types, - Section::FunctionArguments, + Section::ParameterLists, Section::IdentifierSanitization, Section::Whitespace, Section::MaterialEditing, @@ -130,7 +130,7 @@ void WingManual::PrintManual(TSet
Sections, UClass *Handler, bool Abrid "\n Steps do not always require a parameter. For example, materials" "\n only have one graph, so you can just say:" "\n" - "\n /Game/Materials/MyMaterial,graph" + "\n /Game/Materials/MyMaterial,graph" "\n" )); } @@ -142,7 +142,7 @@ void WingManual::PrintManual(TSet
Sections, UClass *Handler, bool Abrid { UWingServer::Print(TEXT( "\n TYPES: Here are some examples of valid types:" - "\n Bool, String, Vector, Rotator, HitResult, Actor, Character," + "\n Bool, String, Vector, Rotator, HitResult, Actor, Character," "\n PlayerController, EBlendMode, EMovementMode, BP_Manny, BP_Quinn," "\n Array, Set, Map" "\n Soft, Class, SoftClass" @@ -157,7 +157,7 @@ void WingManual::PrintManual(TSet
Sections, UClass *Handler, bool Abrid "\n To change variable types, or to express function prototypes, you will" "\n use our syntax for types. Here are some valid examples:" "\n" - "\n Bool, String, Vector, Rotator, HitResult, Actor, Character," + "\n Bool, String, Vector, Rotator, HitResult, Actor, Character," "\n PlayerController, EBlendMode, EMovementMode, BP_Manny, BP_Quinn," "\n Array, Set, Map" "\n Soft, Class, SoftClass" @@ -170,12 +170,12 @@ void WingManual::PrintManual(TSet
Sections, UClass *Handler, bool Abrid } } - if (Sections.Contains(Section::FunctionArguments)) + if (Sections.Contains(Section::ParameterLists)) { if (Abridged) { UWingServer::Print(TEXT( - "\n FUNCTION ARGUMENTS: Here is an example argument list:" + "\n PARAMETER LISTS: Here is an example parameter list:" "\n double D,PlayerController P,Array A" "\n" )); @@ -183,19 +183,19 @@ void WingManual::PrintManual(TSet
Sections, UClass *Handler, bool Abrid else { UWingServer::Print(TEXT( - "\n FUNCTION ARGUMENTS AND RETURN VALUES:" + "\n PARAMETER LISTS:" "\n" - "\n Function argument lists are expressed as comma-separated" - "\n lists of type-name pairs:" + "\n Parameter lists (including function arguments and function return" + "\n values) are expressed as comma-separated lists of type-name pairs:" "\n" - "\n Double D,PlayerController P,Array A" + "\n Double D,PlayerController P,Array A" "\n" - "\n To change the arguments or return values of a function, edit the" - "\n entry or exit node of the graph using GraphNode_SetArgs." - "\n You can view the arguments using GraphNode_Dump. If a return " - "\n node doesn't exist, you may have to create it using GraphNode_Create" - "\n before you can set return values. Custom event nodes also have" - "\n editable arguments." + "\n To change the arguments or return values of a function, edit the" + "\n entry or exit node of the graph using GraphNode_SetArgs." + "\n You can view the arguments using GraphNode_Dump. If a return " + "\n node doesn't exist, you may have to create it using GraphNode_Create" + "\n before you can set return values. Custom event nodes also have" + "\n editable arguments." "\n" )); } @@ -206,9 +206,9 @@ void WingManual::PrintManual(TSet
Sections, UClass *Handler, bool Abrid if (Abridged) { UWingServer::Print(TEXT( - "\n IDENTIFIER SANITIZATION:\n" - "\n Identifiers in unreal can contain whitespace and punctuation.\n" - "\n We sanitize these characters on output:\n" + "\n IDENTIFIER SANITIZATION:" + "\n Identifiers in unreal can contain whitespace and punctuation." + "\n We sanitize these characters on output:" "\n" "\n space → ·" "\n < → ◁" @@ -224,9 +224,9 @@ void WingManual::PrintManual(TSet
Sections, UClass *Handler, bool Abrid else { UWingServer::Print(TEXT( - "\n IDENTIFIER SANITIZATION:\n" + "\n IDENTIFIER SANITIZATION:" "\n" - "\n Identifiers in Unreal can contain spaces and punctuation marks.\n" + "\n Identifiers in Unreal can contain spaces and punctuation marks." "\n Those punctuation marks could confuse our parsers. For example," "\n How would we parse Array if the typename X contained a less-than?" "\n So, we automatically translate these characters on output:" diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingTypes.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingTypes.cpp index 41025a01..5858fed0 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingTypes.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingTypes.cpp @@ -1,6 +1,7 @@ #include "WingTypes.h" #include "WingUtils.h" #include "WingServer.h" +#include "WingManual.h" #include "Editor.h" #include "EdGraphSchema_K2.h" #include "Engine/Blueprint.h" @@ -420,6 +421,12 @@ FString UWingTypes::TypeToTextOrDie(const UObject* Obj) // Tokenizer, Parser, and Resolve Short Name // --------------------------------------------------------------------------- +void UWingTypes::PrintParseError(const TCHAR* Message) +{ + UWingServer::Printf(TEXT("ERROR parsing type '%s' — %s\n"), *ParseInput, Message); + UWingServer::SuggestManual(WingManual::Section::Types); +} + void UWingTypes::Tokenize(const FString& Input) { Tokens.Empty(); @@ -485,7 +492,7 @@ bool UWingTypes::ParseEOF() { if (Cursor != Tokens.Num()) { - Error = TEXT("Extra tokens at end of input"); + PrintParseError(TEXT("extra tokens at end of input")); return false; } return true; @@ -495,7 +502,7 @@ bool UWingTypes::ParseChar(TCHAR c) { if (!TokenIs(c)) { - Error = FString::Printf(TEXT("Expected %c"), c); + PrintParseError(*FString::Printf(TEXT("expected '%c'"), c)); return false; } Cursor++; @@ -506,7 +513,7 @@ bool UWingTypes::ParsePlainIdentifier(FEdGraphPinType& OutType) { if (!TokenIsID()) { - Error = TEXT("Expected Identifier"); + PrintParseError(TEXT("expected identifier")); return false; } FString Name = Tokens[Cursor++]; @@ -520,7 +527,7 @@ bool UWingTypes::ParseWrapped(FName Wrapper, FEdGraphPinType& OutType) if (!ParsePlainIdentifier(OutType)) return false; if (OutType.PinCategory != UEdGraphSchema_K2::PC_Object) { - Error = FString::Printf(TEXT("%s is not an object type"), *OutType.PinSubCategoryObject->GetName()); + PrintParseError(*FString::Printf(TEXT("'%s' is not an object type"), *OutType.PinSubCategoryObject->GetName())); return false; } if (!ParseChar('>')) return false; @@ -599,7 +606,7 @@ bool UWingTypes::ResolveShortName(const FString &Name, FEdGraphPinType &OutType) Info* TypeInfo = ShortToInfo.Find(Name.ToLower()); if (!TypeInfo) { - Error = FString::Printf(TEXT("Unrecognized type '%s'"), *Name); + PrintParseError(*FString::Printf(TEXT("unrecognized type '%s'"), *Name)); return false; } @@ -615,7 +622,7 @@ bool UWingTypes::ResolveShortName(const FString &Name, FEdGraphPinType &OutType) UObject* Obj = LoadObject(nullptr, *TypeInfo->PinSubCategoryObject); if (!Obj) { - Error = FString::Printf(TEXT("Failed to load type '%s' at path '%s'"), *Name, *TypeInfo->PinSubCategoryObject); + PrintParseError(*FString::Printf(TEXT("failed to load type '%s' at path '%s'"), *Name, *TypeInfo->PinSubCategoryObject)); return false; } @@ -666,12 +673,11 @@ bool UWingTypes::TextToType(const FString& Text, FEdGraphPinType& OutPinType, co UWingTypes* Types = GEditor->GetEditorSubsystem(); check(Types); - Types->Error.Empty(); + Types->ParseInput = Text; Types->Tokenize(Text); OutPinType = FEdGraphPinType(); if (!Types->ParseType(OutPinType)) { - UWingServer::Printf(TEXT("%s\n"), *Types->Error); OutPinType = FEdGraphPinType(); return false; } @@ -679,7 +685,8 @@ bool UWingTypes::TextToType(const FString& Text, FEdGraphPinType& OutPinType, co { if (OutPinType.IsContainer()) { - UWingServer::Printf(TEXT("ERROR: Type %s is a container, not allowed here\n"), *Text); + UWingServer::Printf(TEXT("ERROR: Type '%s' is a container, not allowed here\n"), *Text); + UWingServer::SuggestManual(WingManual::Section::HandlerHelp); OutPinType = FEdGraphPinType(); return false; } } @@ -688,8 +695,9 @@ bool UWingTypes::TextToType(const FString& Text, FEdGraphPinType& OutPinType, co { if (OutPinType.PinCategory != Require.PinCategory) { - UWingServer::Printf(TEXT("ERROR: Need a type which is an %s, got a %s instead."), + UWingServer::Printf(TEXT("ERROR: Need a type which is an %s, got a %s instead.\n"), *Require.PinCategory.ToString(), *OutPinType.PinCategory.ToString()); + UWingServer::SuggestManual(WingManual::Section::HandlerHelp); OutPinType = FEdGraphPinType(); return false; } } @@ -699,6 +707,7 @@ bool UWingTypes::TextToType(const FString& Text, FEdGraphPinType& OutPinType, co if (!IsChildOf(OutPinType, Require.IsChildOf)) { UWingServer::Printf(TEXT("ERROR: Type must derive from %s\n"), *WingUtils::FormatName(Require.IsChildOf)); + UWingServer::SuggestManual(WingManual::Section::HandlerHelp); OutPinType = FEdGraphPinType(); return false; } } @@ -708,6 +717,7 @@ bool UWingTypes::TextToType(const FString& Text, FEdGraphPinType& OutPinType, co if (!IsBlueprintType(OutPinType)) { UWingServer::Printf(TEXT("ERROR: Not a blueprint type: %s\n"), *Text); + UWingServer::SuggestManual(WingManual::Section::HandlerHelp); OutPinType = FEdGraphPinType(); return false; } } @@ -717,6 +727,7 @@ bool UWingTypes::TextToType(const FString& Text, FEdGraphPinType& OutPinType, co if (!IsBlueprintable(OutPinType)) { UWingServer::Printf(TEXT("ERROR: Not a blueprintable type: %s\n"), *Text); + UWingServer::SuggestManual(WingManual::Section::HandlerHelp); OutPinType = FEdGraphPinType(); return false; } } @@ -733,6 +744,7 @@ UClass* UWingTypes::TextToOneObjectType(const FString& Text, const Requirements (PinType.IsContainer())) { UWingServer::Printf(TEXT("ERROR: '%s' is not an object class\n"), *Text); + UWingServer::SuggestManual(WingManual::Section::Types); return nullptr; } return Class; @@ -747,6 +759,7 @@ UClass* UWingTypes::TextToOneInterfaceType(const FString& Text, const Requiremen (PinType.IsContainer())) { UWingServer::Printf(TEXT("ERROR: '%s' is not an interface class\n"), *Text); + UWingServer::SuggestManual(WingManual::Section::Types); return nullptr; } return Class; diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingManual.h b/Plugins/UEWingman/Source/UEWingman/Public/WingManual.h index b0459f11..85ba6236 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingManual.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingManual.h @@ -9,7 +9,7 @@ public: HandlerHelp, Paths, Types, - FunctionArguments, + ParameterLists, IdentifierSanitization, Whitespace, MaterialEditing, diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingTypes.h b/Plugins/UEWingman/Source/UEWingman/Public/WingTypes.h index cd5d4b24..723364f3 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingTypes.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingTypes.h @@ -183,5 +183,6 @@ private: // These fields are only used during the parsing of a type. TArray Tokens; int32 Cursor = 0; - FString Error; + FString ParseInput; + void PrintParseError(const TCHAR* Message); };