More work on automatic self-documentation

This commit is contained in:
2026-03-26 17:07:58 -04:00
parent 2bb8baac4c
commit 0f7bfebb71
6 changed files with 64 additions and 42 deletions

View File

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

View File

@@ -55,7 +55,7 @@ bool WingFunctionArgs::ParseArgs(const FString& Args, TArray<FParsedArg>& 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<FParsedArg>& 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<UK2Node_EditablePinBase>(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<FParsedArg> 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<FParsedArg> 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;

View File

@@ -8,7 +8,7 @@ TSet<WingManual::Section> WingManual::AllSections()
return {
Section::Paths,
Section::Types,
Section::FunctionArguments,
Section::ParameterLists,
Section::IdentifierSanitization,
Section::Whitespace,
Section::MaterialEditing,
@@ -170,12 +170,12 @@ void WingManual::PrintManual(TSet<Section> 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<Int> A"
"\n"
));
@@ -183,10 +183,10 @@ void WingManual::PrintManual(TSet<Section> 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<Int> A"
"\n"
@@ -206,9 +206,9 @@ void WingManual::PrintManual(TSet<Section> 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<Section> 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<X> if the typename X contained a less-than?"
"\n So, we automatically translate these characters on output:"

View File

@@ -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<UObject>(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<UWingTypes>();
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;

View File

@@ -9,7 +9,7 @@ public:
HandlerHelp,
Paths,
Types,
FunctionArguments,
ParameterLists,
IdentifierSanitization,
Whitespace,
MaterialEditing,

View File

@@ -183,5 +183,6 @@ private:
// These fields are only used during the parsing of a type.
TArray<FString> Tokens;
int32 Cursor = 0;
FString Error;
FString ParseInput;
void PrintParseError(const TCHAR* Message);
};