1//************************************ bs::framework - Copyright 2018 Marko Pintera **************************************//
2//*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********//
3#pragma once
4
5namespace bs
6{
7 /** @addtogroup Internal-Utility
8 * @{
9 */
10
11 /** @addtogroup String-Internal
12 * @{
13 */
14
15 /** Helper class used for string formatting operations. */
16 class StringFormat
17 {
18 private:
19 /**
20 * Data structure used during string formatting. It holds information about parameter identifiers to replace with
21 * actual parameters.
22 */
23 struct FormatParamRange
24 {
25 FormatParamRange() = default;
26 FormatParamRange(UINT32 start, UINT32 identifierSize, UINT32 paramIdx)
27 :start(start), identifierSize(identifierSize), paramIdx(paramIdx)
28 { }
29
30 UINT32 start = 0;
31 UINT32 identifierSize = 0;
32 UINT32 paramIdx = 0;
33 };
34
35 /** Structure that holds value of a parameter during string formatting. */
36 template<class T>
37 struct ParamData
38 {
39 T* buffer = nullptr;
40 UINT32 size = 0;
41 };
42
43 public:
44 /**
45 * Formats the provided string by replacing the identifiers with the provided parameters. The identifiers are
46 * represented like "{0}, {1}" in the source string, where the number represents the position of the parameter
47 * that will be used for replacing the identifier.
48 *
49 * @note
50 * You may use "\" to escape identifier brackets.
51 * @note
52 * Maximum identifier number is 19 (for a total of 20 unique identifiers. for example {20} won't be recognized as
53 * an identifier).
54 * @note
55 * Total number of parameters that can be referenced is 200.
56 */
57 template<class T, class... Args>
58 static BasicString<T> format(const T* source, Args&& ...args)
59 {
60 UINT32 strLength = getLength(source);
61
62 ParamData<T> parameters[MAX_PARAMS];
63 memset(parameters, 0, sizeof(parameters));
64 getParams(parameters, 0U, std::forward<Args>(args)...);
65
66 T bracketChars[MAX_IDENTIFIER_SIZE + 1];
67 UINT32 bracketWriteIdx = 0;
68
69 FormatParamRange paramRanges[MAX_PARAM_REFERENCES];
70 memset(paramRanges, 0, sizeof(paramRanges));
71 UINT32 paramRangeWriteIdx = 0;
72
73 // Determine parameter positions
74 INT32 lastBracket = -1;
75 bool escaped = false;
76 UINT32 charWriteIdx = 0;
77 for (UINT32 i = 0; i < strLength; i++)
78 {
79 if (source[i] == '\\' && !escaped && paramRangeWriteIdx < MAX_PARAM_REFERENCES)
80 {
81 escaped = true;
82 paramRanges[paramRangeWriteIdx++] = FormatParamRange(charWriteIdx, 1, (UINT32)-1);
83 continue;
84 }
85
86 if (lastBracket == -1)
87 {
88 // If current char is non-escaped opening bracket start parameter definition
89 if (source[i] == '{' && !escaped)
90 lastBracket = i;
91 else
92 charWriteIdx++;
93 }
94 else
95 {
96 if (isdigit(source[i]) && bracketWriteIdx < MAX_IDENTIFIER_SIZE)
97 bracketChars[bracketWriteIdx++] = source[i];
98 else
99 {
100 // If current char is non-escaped closing bracket end parameter definition
101 UINT32 numParamChars = bracketWriteIdx;
102 bool processedBracket = false;
103 if (source[i] == '}' && numParamChars > 0 && !escaped)
104 {
105 bracketChars[bracketWriteIdx] = '\0';
106 UINT32 paramIdx = strToInt(bracketChars);
107 if (paramIdx < MAX_PARAMS && paramRangeWriteIdx < MAX_PARAM_REFERENCES) // Check if exceeded maximum parameter limit
108 {
109 paramRanges[paramRangeWriteIdx++] = FormatParamRange(charWriteIdx, numParamChars + 2, paramIdx);
110 charWriteIdx += parameters[paramIdx].size;
111
112 processedBracket = true;
113 }
114 }
115
116 if (!processedBracket)
117 {
118 // Last bracket wasn't really a parameter
119 for (UINT32 j = lastBracket; j <= i; j++)
120 charWriteIdx++;
121 }
122
123 lastBracket = -1;
124 bracketWriteIdx = 0;
125 }
126 }
127
128 escaped = false;
129 }
130
131 // Copy the clean string into output buffer
132 UINT32 finalStringSize = charWriteIdx;
133
134 T* outputBuffer = (T*)bs_alloc(finalStringSize * sizeof(T));
135 UINT32 copySourceIdx = 0;
136 UINT32 copyDestIdx = 0;
137 for (UINT32 i = 0; i < paramRangeWriteIdx; i++)
138 {
139 const FormatParamRange& rangeInfo = paramRanges[i];
140 UINT32 copySize = rangeInfo.start - copyDestIdx;
141
142 memcpy(outputBuffer + copyDestIdx, source + copySourceIdx, copySize * sizeof(T));
143 copySourceIdx += copySize + rangeInfo.identifierSize;
144 copyDestIdx += copySize;
145
146 if (rangeInfo.paramIdx == (UINT32)-1)
147 continue;
148
149 UINT32 paramSize = parameters[rangeInfo.paramIdx].size;
150 memcpy(outputBuffer + copyDestIdx, parameters[rangeInfo.paramIdx].buffer, paramSize * sizeof(T));
151 copyDestIdx += paramSize;
152 }
153
154 memcpy(outputBuffer + copyDestIdx, source + copySourceIdx, (finalStringSize - copyDestIdx) * sizeof(T));
155
156 BasicString<T> outputStr(outputBuffer, finalStringSize);
157 bs_free(outputBuffer);
158
159 for (UINT32 i = 0; i < MAX_PARAMS; i++)
160 {
161 if (parameters[i].buffer != nullptr)
162 bs_free(parameters[i].buffer);
163 }
164
165 return outputStr;
166 }
167
168 private:
169 /**
170 * Set of methods that can be specialized so we have a generalized way for retrieving length of strings of
171 * different types.
172 */
173 static UINT32 getLength(const char* source) { return (UINT32)strlen(source); }
174
175 /**
176 * Set of methods that can be specialized so we have a generalized way for retrieving length of strings of
177 * different types.
178 */
179 static UINT32 getLength(const wchar_t* source) { return (UINT32)wcslen(source); }
180
181 /** Parses the string and returns an integer value extracted from string characters. */
182 static UINT32 strToInt(const char* buffer)
183 {
184 return (UINT32)strtoul(buffer, nullptr, 10);
185 }
186
187 /** Parses the string and returns an integer value extracted from string characters. */
188 static UINT32 strToInt(const wchar_t* buffer)
189 {
190 return (UINT32)wcstoul(buffer, nullptr, 10);
191 }
192
193 /** Helper method for converting any data type to a narrow string. */
194 template<class T> static std::string toString(const T& param) { return std::to_string(param); }
195
196 /** Helper method that "converts" a narrow string to a narrow string (simply a pass through). */
197 static std::string toString(const std::string& param) { return param; }
198
199 /** Helper method that converts a framework narrow string to a standard narrow string. */
200 static std::string toString(const String& param)
201 {
202 return std::string(param.c_str());
203 }
204
205 /** Helper method that converts a narrow character array to a narrow string. */
206 template<class T> static std::string toString(T* param)
207 {
208 static_assert(!std::is_same<T,T>::value, "Invalid pointer type.");
209 return "";
210 }
211
212 /** Helper method that converts a narrow character array to a narrow string. */
213 static std::string toString(const char* param)
214 {
215 if (param == nullptr)
216 return std::string();
217
218 return std::string(param);
219 }
220
221 /** Helper method that converts a narrow character array to a narrow string. */
222 static std::string toString(char* param)
223 {
224 if (param == nullptr)
225 return std::string();
226
227 return std::string(param);
228 }
229
230 /** Helper method for converting any data type to a wide string. */
231 template<class T> static std::wstring toWString(const T& param) { return std::to_wstring(param); }
232
233 /** Helper method that "converts" a wide string to a wide string (simply a pass through). */
234 static std::wstring toWString(const std::wstring& param) { return param; }
235
236 /** Helper method that converts a framework wide string to a standard wide string. */
237 static std::wstring toWString(const WString& param)
238 {
239 return std::wstring(param.c_str());
240 }
241
242 /** Helper method that converts a wide character array to a wide string. */
243 template<class T> static std::wstring toWString(T* param)
244 {
245 static_assert(!std::is_same<T,T>::value, "Invalid pointer type.");
246 return L"";
247 }
248
249 /** Helper method that converts a wide character array to a wide string. */
250 static std::wstring toWString(const wchar_t* param)
251 {
252 if (param == nullptr)
253 return std::wstring();
254
255 return std::wstring(param);
256 }
257
258 /** Helper method that converts a wide character array to a wide string. */
259 static std::wstring toWString(wchar_t* param)
260 {
261 if (param == nullptr)
262 return std::wstring();
263
264 return std::wstring(param);
265 }
266
267 /**
268 * Converts all the provided parameters into string representations and populates the provided @p parameters array.
269 */
270 template<class P, class... Args>
271 static void getParams(ParamData<char>* parameters, UINT32 idx, P&& param, Args&& ...args)
272 {
273 if (idx >= MAX_PARAMS)
274 return;
275
276 std::basic_string<char> sourceParam = toString(param);
277 parameters[idx].buffer = (char*)bs_alloc((UINT32)sourceParam.size() * sizeof(char));
278 parameters[idx].size = (UINT32)sourceParam.size();
279
280 sourceParam.copy(parameters[idx].buffer, parameters[idx].size, 0);
281
282 getParams(parameters, idx + 1, std::forward<Args>(args)...);
283 }
284
285 /**
286 * Converts all the provided parameters into string representations and populates the provided @p parameters array.
287 */
288 template<class P, class... Args>
289 static void getParams(ParamData<wchar_t>* parameters, UINT32 idx, P&& param, Args&& ...args)
290 {
291 if (idx >= MAX_PARAMS)
292 return;
293
294 std::basic_string<wchar_t> sourceParam = toWString(param);
295 parameters[idx].buffer = (wchar_t*)bs_alloc((UINT32)sourceParam.size() * sizeof(wchar_t));
296 parameters[idx].size = (UINT32)sourceParam.size();
297
298 sourceParam.copy(parameters[idx].buffer, parameters[idx].size, 0);
299
300 getParams(parameters, idx + 1, std::forward<Args>(args)...);
301 }
302
303 /** Helper method for parameter size calculation. Used as a stopping point in template recursion. */
304 static void getParams(ParamData<char>* parameters, UINT32 idx)
305 {
306 // Do nothing
307 }
308
309 /** Helper method for parameter size calculation. Used as a stopping point in template recursion. */
310 static void getParams(ParamData<wchar_t>* parameters, UINT32 idx)
311 {
312 // Do nothing
313 }
314
315 static constexpr const UINT32 MAX_PARAMS = 20;
316 static constexpr const UINT32 MAX_IDENTIFIER_SIZE = 2;
317 static constexpr const UINT32 MAX_PARAM_REFERENCES = 200;
318 };
319
320 /** @} */
321 /** @} */
322}
323