MCRO
C++23 utilities for Unreal Engine.
Loading...
Searching...
No Matches
FmtMacros.h
Go to the documentation of this file.
1/** @noop License Comment
2 * @file
3 * @copyright
4 * This Source Code is subject to the terms of the Mozilla Public License, v2.0.
5 * If a copy of the MPL was not distributed with this file You can obtain one at
6 * https://mozilla.org/MPL/2.0/
7 *
8 * @author David Mórász
9 * @date 2025
10 */
11
12/**
13 * @file
14 * @brief
15 * Use leading `FMT_` or trailing `_FMT` fake text literals to create modern formatted strings with a better API.
16 *
17 * The major difference from `PRINTF_` or `FString::Printf(...)` is that `FMT` macros can take user defined string
18 * conversions into account, so more types can be used directly as arguments.
19 *
20 * @todo
21 * Make a unified way to handle format arguments for FText and FString. Currently _FMT on FText is using string
22 * conversions to do the actual formatting, and not vanilla FText::Format
23 */
24
25#pragma once
26
27#include <string>
28
29#include "CoreMinimal.h"
30#include "Mcro/Text.h"
31#include "Mcro/TextMacros.h"
32#include "Mcro/Enums.h"
33#include "Mcro/Macros.h"
34
35template <size_t N>
36FString operator % (FStringFormatOrderedArguments&& args, const TCHAR(& format)[N])
37{
38 FString result = FString::Format(format, args);
39 result.TrimToNullTerminator();
40 return result;
41}
42
43template <size_t N>
44FString operator % (const TCHAR(& format)[N], FStringFormatOrderedArguments&& args)
45{
46 FString result = FString::Format(format, args);
47 result.TrimToNullTerminator();
48 return result;
49}
50
51template <size_t N>
52FString operator % (FStringFormatNamedArguments&& args, const TCHAR(& format)[N])
53{
54 FString result = FString::Format(format, args);
55 result.TrimToNullTerminator();
56 return result;
57}
58
59template <size_t N>
60FString operator % (const TCHAR(& format)[N], FStringFormatNamedArguments&& args)
61{
62 FString result = FString::Format(format, args);
63 result.TrimToNullTerminator();
64 return result;
65}
66
67FORCEINLINE FText operator % (FText const& format, FStringFormatOrderedArguments&& args)
68{
69 FString result = FString::Format(*format.ToString(), args);
70 result.TrimToNullTerminator();
71 return FText::FromString(result);
72}
73
74FORCEINLINE FText operator % (FText const& format, FStringFormatNamedArguments&& args)
75{
76 FString result = FString::Format(*format.ToString(), args);
77 result.TrimToNullTerminator();
78 return FText::FromString(result);
79}
80
81#define MCRO_FMT_NAMED_ARG_TRANSFORM(s, data, elem) BOOST_PP_EXPAND(MCRO_FMT_NAMED_ARG elem)
82#define MCRO_FMT_NAMED_ARG(key, value) MakeTuple(FString(TEXT(#key)), value)
83
84#define MCRO_FMT_NAMED(seq) \
85 BOOST_PP_IIF(BOOST_PP_IS_BEGIN_PARENS(seq), \
86 MCRO_FMT_NAMED_0, \
87 BOOST_PP_IDENTITY_N(, 1) \
88 )(seq) //
89
90#define MCRO_FMT_NAMED_0(seq) \
91 Mcro::Text::NamedArguments( \
92 BOOST_PP_SEQ_ENUM( \
93 BOOST_PP_SEQ_TRANSFORM( \
94 MCRO_FMT_NAMED_ARG_TRANSFORM, , \
95 BOOST_PP_VARIADIC_SEQ_TO_SEQ(seq) \
96 ) \
97 ) \
98 ) //
99
100#define MCRO_FMT_ORDERED(...) Mcro::Text::OrderedArguments(__VA_ARGS__)
101
102#define MCRO_FMT_ARGS(...) \
103 BOOST_PP_IIF(BOOST_PP_IS_BEGIN_PARENS(__VA_ARGS__), \
104 MCRO_FMT_NAMED(BOOST_PP_VARIADIC_ELEM(0, __VA_ARGS__)), \
105 MCRO_FMT_ORDERED(__VA_ARGS__) \
106 ) //
107
108/**
109 * @brief
110 * Leading fake text literal which makes using `FString::Format(...);` much more comfortable.
111 *
112 * `FMT` macros allow more types to be used directly in the format arguments expression because `Mcro::Text` has
113 * a couple of conversion utilities. If the first argument of `FMT_` is a sequence of `("key", value)` pairs enclosed
114 * in parentheses, then named format arguments are assumed. Ordered format arguments are assumed otherwise. The two
115 * modes cannot be mixed.
116 *
117 * Usage:
118 * @code
119 * EPixelFormat format = PF_Unknown; int32 num = 42;
120 *
121 * FString ordered = FMT_(format, num) "Hi {0}, your number is {1}";
122 * // -> "Hi PF_Unknown, your number is 42"
123 *
124 * // | Notice the lack of comma here
125 * // V
126 * FString named = FMT_((Type, format) (Count, num)) "Hi {Type}, your number is {Count}";
127 * // -> "Hi PF_Unknown, your number is 42"
128 * @endcode
129 *
130 * The following argument types are supported out-of-box:
131 * - Originally supported by `FStringFormatArg` and what's implicitly convertable to
132 * - `FString, FStringView, int32, uint32, int64, uint64, float, double`
133 * - `ANSICHAR, WIDECHAR, UCS2CHAR, UTF8CHAR` pointer strings
134 * - Anything which has a `ToString()` member method which produces one of the above type
135 * - Including but not exclusively `FText` and `FName` for example
136 * - STL strings and views of any encoding
137 * - Enums where their entries are serialized as human-readable names
138 *
139 * @remarks
140 * To add more supported types specialize the `TAsFormatArgument` template functor for your preferred type and return
141 * a value which is implicitly convertible to `FStringFormatArg` in the `Mcro::Text` namespace. For example check
142 * `Enums.h` to see how that's done with enums. For your own types you can also implement a `ToString()` member
143 * method to get automatic support.
144 *
145 * @warning
146 * The **named arguments** overload (`FMT_((A, a) (B, b) (C, c))`) must not have comma `,` between the individual
147 * pairs. This is because named argument pairs are passed into this macro as a "sequence" instead of variadic arguments.
148 * Ordered format arguments however should be passed in as regular variadic arguments separated by comma `,` as
149 * nature intended.
150 *
151 * @note
152 * `FMT` macros are the only things in MCRO where excessive preprocessing is used
153 */
154#define FMT_(...) MCRO_FMT_ARGS(__VA_ARGS__) % TEXT_
155
156/**
157 * @brief
158 * Trailing fake text literal which makes using `FString::Format(...);` much more comfortable.
159 *
160 * `FMT` macros allow more types to be used directly in the format arguments expression because `Mcro::Text` has
161 * a couple of conversion utilities. If the first argument of `_FMT` is a sequence of `("key", value)` pairs enclosed
162 * in parentheses, then named format arguments are assumed. Ordered format arguments are assumed otherwise. The two
163 * modes cannot be mixed.
164 *
165 * Usage:
166 * @code
167 * EPixelFormat format = PF_Unknown; int32 num = 42;
168 *
169 * FString ordered = TEXT_"Hi {0}, your number is {1}" _FMT(format, num);
170 * // -> "Hi PF_Unknown, your number is 42" ^ this space is important
171 *
172 * // Named arguments look better with multiple lines on this version
173 * FString named = TEXT_"Hi {Type}, your number is {Count}" _FMT(
174 * (Type, format) // <- Notice the lack of comma here ^ this space is important
175 * (Count, num)
176 * );
177 * // -> "Hi PF_Unknown, your number is 42"
178 * @endcode
179 *
180 * The following argument types are supported out-of-box:
181 * - Originally supported by `FStringFormatArg` and what's implicitly convertable to
182 * - `FString, FStringView, int32, uint32, int64, uint64, float, double`
183 * - `ANSICHAR, WIDECHAR, UCS2CHAR, UTF8CHAR` pointer strings
184 * - Anything which has a `ToString()` member method which produces one of the above type
185 * - Including but not exclusively `FText` and `FName` for example
186 * - STL strings and views of any encoding
187 * - Enums where their entries are serialized as human-readable names
188 *
189 * This variant also allows to operate on `FText` instead of `const TCHAR*` strings if the fake string literal macro
190 * before `_FMT` yields FText. This version however still uses `FString`'s under the hood, and it is not yet using
191 * vanilla `FText::Format` features
192 *
193 * @remarks
194 * To add more supported types specialize the `TAsFormatArgument` template functor for your preferred type and return
195 * a value which is implicitly convertible to `FStringFormatArg` in the `Mcro::Text` namespace. For example check
196 * `Enums.h` to see how that's done with enums. For your own types you can also implement a `ToString()` member
197 * method to get automatic support.
198 *
199 * @warning
200 * The **named arguments** overload (`_FMT((A, a) (B, b) (C, c))`) must not have comma `,` between the individual
201 * pairs. This is because named argument pairs are passed into this macro as a "sequence" instead of variadic arguments.
202 * Ordered format arguments however should be passed in as regular variadic arguments separated by comma `,` as
203 * nature intended.
204 *
205 * @note
206 * `FMT` macros are the only things in MCRO where excessive preprocessing is used
207 */
208#define _FMT(...) % MCRO_FMT_ARGS(__VA_ARGS__)
209
210/**
211 * @brief
212 * Similar to `UE_LOGFMT`, but implemented via MCRO's own `_FMT` macro. So it's more convenient to pass format arguments.
213 *
214 * This is naively implemented via regular UE_LOG which gets a single string format argument which is fed the result of
215 * the `_FMT` macro. `UE_LOGFMT` may have better performance or may have more insights to format arguments.
216 *
217 * The same argument syntax applies here as with `_FMT` regarding the distinction between named and ordered arguments.
218 *
219 * Named arguments:
220 * @code
221 * // Vanilla:
222 * UE_LOGFMT(LogSpaceMouseConfig, Display, "Input Binding {Context} / {Name} is handled",
223 * cmd.GetBindingContext().ToString(),
224 * cmd.GetCommandName().ToString()
225 * );
226 * // MCRO:
227 * FMT_LOG(LogSpaceMouseConfig, Display, "Input Binding {Context} / {Name} is handled",
228 * (Context, cmd.GetBindingContext()) // <- Notice the lack of comma here!
229 * (Name, cmd.GetCommandName())
230 * );
231 * @endcode
232 * Ordered arguments:
233 * @code
234 * // Vanilla:
235 * UE_LOGFMT(LogSpaceMouseConfig, Display, "Input Binding {0} / {1} is handled",
236 * cmd.GetBindingContext().ToString(),
237 * cmd.GetCommandName().ToString()
238 * );
239 * // MCRO:
240 * FMT_LOG(LogSpaceMouseConfig, Display, "Input Binding {0} / {1} is handled",
241 * cmd.GetBindingContext(),
242 * cmd.GetCommandName()
243 * );
244 * @endcode
245 */
246#define FMT_LOG(categoryName, verbosity, format, ...) \
247 UE_LOG(categoryName, verbosity, TEXT("%s"), *(TEXT(format) _FMT(__VA_ARGS__)))
FString operator%(FStringFormatOrderedArguments &&args, const TCHAR(&format)[N])
Definition FmtMacros.h:36
Use leading TEXT_ without parenthesis for Unreal compatible text literals.