TValueOrError<FString, FExpressionError> FStringFormatter::FormatInternal(const TCHAR* InExpression, const TMap<FString, FStringFormatArg>& Args, bool bStrict) const
{
	TValueOrError<TArray<FExpressionToken>, FExpressionError> Result = ExpressionParser::Lex(InExpression, bStrict ? StrictNamedDefinitions : NamedDefinitions);
	if (!Result.IsValid())
	{
		return MakeError(Result.StealError());
	}

	TArray<FExpressionToken>& Tokens = Result.GetValue();
	if (Tokens.Num() == 0)
	{
		return MakeValue(InExpression);
	}
	
	// This code deliberately tries to reallocate as little as possible
	FString Formatted;
	Formatted.Reserve(Tokens.Last().Context.GetTokenEndPos() - InExpression);
	for (auto& Token : Tokens)
	{
		if (const auto* Literal = Token.Node.Cast<FStringLiteral>())
		{
			Formatted.AppendChars(Literal->String.GetTokenStartPos(), Literal->Len);
		}
		else if (auto* Escaped = Token.Node.Cast<FEscapedCharacter>())
		{
			Formatted.AppendChar(Escaped->Character);
		}
		else if (const auto* FormatToken = Token.Node.Cast<FFormatSpecifier>())
		{
			const FStringFormatArg* Arg = nullptr;
			for (auto& Pair : Args)
			{
				if (Pair.Key.Len() == FormatToken->Len && FCString::Strnicmp(FormatToken->Identifier.GetTokenStartPos(), *Pair.Key, FormatToken->Len) == 0)
				{
					Arg = &Pair.Value;
					break;
				}
			}

			if (Arg)
			{
				AppendToString(*Arg, Formatted);
			}
			else if (bStrict)
			{
				return MakeError(FText::Format(LOCTEXT("UndefinedFormatSpecifier", "Undefined format token: {0}"), FText::FromString(FormatToken->Identifier.GetString())));
			}
			else
			{
				// No replacement found, so just add the original token string
				const int32 Length = FormatToken->EntireToken.GetTokenEndPos() - FormatToken->EntireToken.GetTokenStartPos();
				Formatted.AppendChars(FormatToken->EntireToken.GetTokenStartPos(), Length);
			}
		}
	}

	return MakeValue(Formatted);
}
TValueOrError<FString, FExpressionError> FStringFormatter::FormatInternal(const TCHAR* InExpression, const TArray<FStringFormatArg>& Args, bool bStrict) const
{
	TValueOrError<TArray<FExpressionToken>, FExpressionError> Result = ExpressionParser::Lex(InExpression, bStrict ? StrictOrderedDefinitions : OrderedDefinitions);
	if (!Result.IsValid())
	{
		return MakeError(Result.StealError());
	}

	TArray<FExpressionToken>& Tokens = Result.GetValue();
	if (Tokens.Num() == 0)
	{
		return MakeValue(InExpression);
	}
	
	// This code deliberately tries to reallocate as little as possible
	FString Formatted;
	Formatted.Reserve(Tokens.Last().Context.GetTokenEndPos() - InExpression);
	for (auto& Token : Tokens)
	{
		if (const auto* Literal = Token.Node.Cast<FStringLiteral>())
		{
			Formatted.AppendChars(Literal->String.GetTokenStartPos(), Literal->Len);
		}
		else if (auto* Escaped = Token.Node.Cast<FEscapedCharacter>())
		{
			Formatted.AppendChar(Escaped->Character);
		}
		else if (const auto* IndexToken = Token.Node.Cast<FIndexSpecifier>())
		{
			if (Args.IsValidIndex(IndexToken->Index))
			{
				AppendToString(Args[IndexToken->Index], Formatted);
			}
			else if (bStrict)
			{
				return MakeError(FText::Format(LOCTEXT("InvalidArgumentIndex", "Invalid argument index: {0}"), FText::AsNumber(IndexToken->Index)));
			}
			else
			{
				// No replacement found, so just add the original token string
				const int32 Length = IndexToken->EntireToken.GetTokenEndPos() - IndexToken->EntireToken.GetTokenStartPos();
				Formatted.AppendChars(IndexToken->EntireToken.GetTokenStartPos(), Length);
			}
		}
	}

	return MakeValue(Formatted);
}