#include "SlashCommand.h" #include "CoreMinimal.h" #include "Containers/StringView.h" #include "Misc/Char.h" #include "Misc/DefaultValueHelper.h" ////////////////////////////////////////////////////////////// // // SlashCommand // // A command line plus a cursor, exposed to blueprint. // ////////////////////////////////////////////////////////////// UlxSlashCommand* UlxSlashCommand::MakeSlashCommand(const FString& CommandLine) { UlxSlashCommand* Result = NewObject(); Result->CommandLine = CommandLine; return Result; } bool UlxSlashCommand::IsSlashCommand(const FString& Command) { if (Command.Len() < 2 || Command[0] != TEXT('/')) { return false; } // Every character after the slash must be alphanumeric. for (int32 i = 1; i < Command.Len(); i++) { if (!FChar::IsAlnum(Command[i])) { return false; } } return true; } FStringView UlxSlashCommand::FetchToken() { const TCHAR* Data = *CommandLine; int32 Len = CommandLine.Len(); // Skip leading whitespace. while (Cursor < Len && FChar::IsWhitespace(Data[Cursor])) { Cursor++; } // Read characters up to the next whitespace. int32 Start = Cursor; while (Cursor < Len && !FChar::IsWhitespace(Data[Cursor])) { Cursor++; } return FStringView(Data + Start, Cursor - Start); } ElxSuccessOrError UlxSlashCommand::CheckCommand(const FString& Literal, const FString& Prototype) { KnownCommands.Add(Literal); // Checking the command always starts a fresh parse. Cursor = 0; FStringView Token = FetchToken(); if (Token.Equals(Literal, ESearchCase::IgnoreCase)) { MatchingPrototypes.Add(Prototype); return ElxSuccessOrError::Success; } return ElxSuccessOrError::Error; } ElxSuccessOrError UlxSlashCommand::ReadToken(FString& Result) { FStringView Token = FetchToken(); if (Token.IsEmpty()) { Result.Empty(); return ElxSuccessOrError::Error; } Result = FString(Token); return ElxSuccessOrError::Success; } ElxSuccessOrError UlxSlashCommand::ReadInteger(int32& Result) { // ParseInt validates the whole token and converts it, so "12abc" // is rejected rather than read as a partial number. FStringView Token = FetchToken(); if (!FDefaultValueHelper::ParseInt(FString(Token), Result)) { Result = 0; return ElxSuccessOrError::Error; } return ElxSuccessOrError::Success; } ElxSuccessOrError UlxSlashCommand::ReadFloat(double& Result) { // ParseDouble validates the whole token and converts it, so // "12abc" is rejected rather than read as a partial number. FStringView Token = FetchToken(); if (!FDefaultValueHelper::ParseDouble(FString(Token), Result)) { Result = 0.0; return ElxSuccessOrError::Error; } return ElxSuccessOrError::Success; } ElxSuccessOrError UlxSlashCommand::ReadRest(FString& Result) { const TCHAR* Data = *CommandLine; int32 Len = CommandLine.Len(); // Everything from the cursor to the end of the line, trimmed. Result = FString(FStringView(Data + Cursor, Len - Cursor)); Result.TrimStartAndEndInline(); Cursor = Len; return ElxSuccessOrError::Success; } FString UlxSlashCommand::GetErrorMessage() const { if (MatchingPrototypes.Num() == 0) { TArray Commands; for (const FString& Command : KnownCommands) { Commands.Add(Command); } Commands.Sort(); return FString(TEXT("No such slash command. Valid slash commands: ")) + FString::Join(Commands, TEXT(", ")); } FString Result = TEXT("Invalid parameters. Valid parameters:\n"); for (const FString& Prototype : MatchingPrototypes) { Result += Prototype; Result += TEXT("\n"); } return Result; }