Sane C++ Libraries
C++ Platform Abstraction Libraries
Loading...
Searching...
No Matches
StringFormat.h
1// Copyright (c) Stefano Cristiano
2// SPDX-License-Identifier: MIT
3#pragma once
4#include "../Foundation/AlignedStorage.h"
5#include "../Foundation/Internal/IGrowableBuffer.h"
6#include "../Strings/StringView.h"
7
8namespace SC
9{
10struct Console;
11
12struct StringFormatOutput;
13template <typename T>
14struct StringFormatterFor
15{
16 static bool format(StringFormatOutput& data, const StringSpan specifier, const T& value)
17 {
18 return StringFormatterFor<decltype(value.view())>::format(data, specifier, value.view());
19 };
20};
21
25struct SC_COMPILER_EXPORT StringFormatOutput
26{
30 template <typename T>
31 StringFormatOutput(StringEncoding encoding, T& destination) : encoding(encoding)
32 {
33 GrowableBuffer<T>& gbuf = growableBufferStorage.reinterpret_as<GrowableBuffer<T>>();
34 placementNew(gbuf, destination);
35 growableBuffer = &gbuf;
36 }
37
38 StringFormatOutput(StringEncoding encoding, IGrowableBuffer& buffer) : encoding(encoding)
39 {
40 growableBuffer = &buffer;
41 destroyBuffer = false;
42 }
43
44 ~StringFormatOutput();
45
49 StringFormatOutput(StringEncoding encoding, Console& destination);
50
54 [[nodiscard]] bool append(StringView text);
55
58
61
64 [[nodiscard]] bool onFormatSucceeded();
65
66 private:
67 AlignedStorage<6 * sizeof(void*)> growableBufferStorage;
68
69 bool destroyBuffer = true;
70 IGrowableBuffer* growableBuffer = nullptr;
71 StringEncoding encoding;
72
73 Console* console = nullptr;
74 size_t backupSize = 0;
75};
76
98template <typename RangeIterator>
100{
107 template <typename... Types>
108 [[nodiscard]] static bool format(StringFormatOutput& data, StringView fmt, Types&&... args);
109
110 private:
111 struct Implementation;
112};
114
115} // namespace SC
116
117//-----------------------------------------------------------------------------------------------------------------------
118// Implementations Details
119//-----------------------------------------------------------------------------------------------------------------------
120template <typename RangeIterator>
121struct SC::StringFormat<RangeIterator>::Implementation
122{
123 template <int Total, int N, typename T, typename... Rest>
124 static bool formatArgument(StringFormatOutput& data, StringView specifier, int position, T&& arg, Rest&&... rest)
125 {
126 if (position == Total - N)
127 {
128 using First = typename TypeTraits::RemoveConst<typename TypeTraits::RemoveReference<T>::type>::type;
129 return StringFormatterFor<First>::format(data, specifier, arg);
130 }
131 else
132 {
133 return formatArgument<Total, N - 1>(data, specifier, position, forward<Rest>(rest)...);
134 }
135 }
136
137 template <int Total, int N, typename... Args>
138 static typename SC::TypeTraits::EnableIf<sizeof...(Args) == 0, bool>::type formatArgument(StringFormatOutput&,
139 StringView, int, Args...)
140 {
141 return false;
142 }
143
144 template <typename... Types>
145 static bool parsePosition(StringFormatOutput& data, RangeIterator& it, int32_t& parsedPosition, Types&&... args)
146 {
147 const auto startOfSpecifier = it;
148 if (it.advanceUntilMatches('}')) // We have an already matched '{' when arriving here
149 {
150 auto specifier = startOfSpecifier.sliceFromStartUntil(it);
151 auto specifierPosition = specifier;
152 if (specifier.advanceUntilMatches(':'))
153 {
154 specifierPosition = startOfSpecifier.sliceFromStartUntil(specifier);
155 (void)specifier.stepForward(); // eat '{'
156 }
157 (void)specifierPosition.stepForward(); // eat '{'
158 (void)it.stepForward(); // eat '}'
159 const StringView positionString = StringView::fromIteratorUntilEnd(specifierPosition);
160 const StringView specifierString = StringView::fromIteratorUntilEnd(specifier);
161 if (not positionString.isEmpty())
162 {
163 if (not positionString.parseInt32(parsedPosition))
164 {
165 return false;
166 }
167 }
168 constexpr auto maxArgs = sizeof...(args);
169 return formatArgument<maxArgs, maxArgs>(data, specifierString, parsedPosition, forward<Types>(args)...);
170 }
171 return false;
172 }
173
174 template <typename... Types>
175 static bool executeFormat(StringFormatOutput& data, RangeIterator it, Types&&... args)
176 {
177 StringCodePoint matchedChar;
178
179 auto start = it;
180 int32_t position = 0;
181 int32_t maxPosition = 0;
182 while (true)
183 {
184 if (it.advanceUntilMatchesAny({'{', '}'}, matchedChar)) // match start or end of specifier
185 {
186 if (it.isFollowedBy(matchedChar))
187 SC_LANGUAGE_UNLIKELY // if it's the same matched, let's escape it
188 {
189 (void)it.stepForward(); // we want to make sure we insert the escaped '{' or '}'
190 if (not data.append(StringView::fromIterators(start, it)))
191 return false;
192 (void)it.stepForward(); // we don't want to insert the additional '{' or '}' needed for escaping
193 start = it;
194 }
195 else if (matchedChar == '{') // it's a '{' not followed by itself, so let's parse specifier
196 {
197 if (not data.append(StringView::fromIterators(start, it))) // write everything before '{
198 return false;
199 // try parse '}' and eventually format
200 int32_t parsedPosition = position;
201 if (not parsePosition(data, it, parsedPosition, forward<Types>(args)...))
202 return false;
203 start = it;
204 position += 1;
205 maxPosition = max(maxPosition, parsedPosition + 1);
206 }
207 else
208 {
209 return false; // arriving here means end of string with as single, unescaped '}'
210 }
211 }
212 else
213 {
214 if (not data.append(StringView::fromIterators(start, it))) // write everything before '{
215 return false;
216 return maxPosition == static_cast<int32_t>(sizeof...(args)); // check right number of args
217 }
218 }
219 }
220};
221
222template <typename RangeIterator>
223template <typename... Types>
225{
226 data.onFormatBegin();
227 if (Implementation::executeFormat(data, fmt.getIterator<RangeIterator>(), forward<Types>(args)...))
228 SC_LANGUAGE_LIKELY { return data.onFormatSucceeded(); }
229 else
230 {
231 data.onFormatFailed();
232 return false;
233 }
234}
235
236namespace SC
237{
238// clang-format off
239template <> struct SC_COMPILER_EXPORT StringFormatterFor<float> {static bool format(StringFormatOutput&, const StringSpan, const float);};
240template <> struct SC_COMPILER_EXPORT StringFormatterFor<double> {static bool format(StringFormatOutput&, const StringSpan, const double);};
241#if SC_COMPILER_MSVC || SC_COMPILER_CLANG_CL
242#if SC_PLATFORM_64_BIT == 0
243template <> struct SC_COMPILER_EXPORT StringFormatterFor<SC::ssize_t> {static bool format(StringFormatOutput&, const StringSpan, const SC::ssize_t);};
244#endif
245#else
246#if !SC_PLATFORM_LINUX
247template <> struct SC_COMPILER_EXPORT StringFormatterFor<SC::size_t> {static bool format(StringFormatOutput&, const StringSpan, const SC::size_t);};
248template <> struct SC_COMPILER_EXPORT StringFormatterFor<SC::ssize_t> {static bool format(StringFormatOutput&, const StringSpan, const SC::ssize_t);};
249#endif
250#endif
251template <> struct SC_COMPILER_EXPORT StringFormatterFor<SC::int64_t> {static bool format(StringFormatOutput&, const StringSpan, const SC::int64_t);};
252template <> struct SC_COMPILER_EXPORT StringFormatterFor<SC::uint64_t> {static bool format(StringFormatOutput&, const StringSpan, const SC::uint64_t);};
253template <> struct SC_COMPILER_EXPORT StringFormatterFor<SC::int32_t> {static bool format(StringFormatOutput&, const StringSpan, const SC::int32_t);};
254template <> struct SC_COMPILER_EXPORT StringFormatterFor<SC::uint32_t> {static bool format(StringFormatOutput&, const StringSpan, const SC::uint32_t);};
255template <> struct SC_COMPILER_EXPORT StringFormatterFor<SC::int16_t> {static bool format(StringFormatOutput&, const StringSpan, const SC::int16_t);};
256template <> struct SC_COMPILER_EXPORT StringFormatterFor<SC::uint16_t> {static bool format(StringFormatOutput&, const StringSpan, const SC::uint16_t);};
257template <> struct SC_COMPILER_EXPORT StringFormatterFor<SC::int8_t> {static bool format(StringFormatOutput&, const StringSpan, const SC::int8_t);};
258template <> struct SC_COMPILER_EXPORT StringFormatterFor<SC::uint8_t> {static bool format(StringFormatOutput&, const StringSpan, const SC::uint8_t);};
259template <> struct SC_COMPILER_EXPORT StringFormatterFor<char> {static bool format(StringFormatOutput&, const StringSpan, const char);};
260template <> struct SC_COMPILER_EXPORT StringFormatterFor<bool> {static bool format(StringFormatOutput&, const StringSpan, const bool);};
261template <> struct SC_COMPILER_EXPORT StringFormatterFor<StringView> {static bool format(StringFormatOutput&, const StringSpan, const StringView);};
262template <> struct SC_COMPILER_EXPORT StringFormatterFor<const char*> {static bool format(StringFormatOutput&, const StringSpan, const char*);};
263template <> struct SC_COMPILER_EXPORT StringFormatterFor<const void*> {static bool format(StringFormatOutput&, const StringSpan, const void*);};
264#if SC_PLATFORM_WINDOWS
265template <> struct SC_COMPILER_EXPORT StringFormatterFor<wchar_t> {static bool format(StringFormatOutput&, const StringSpan, const wchar_t);};
266template <> struct SC_COMPILER_EXPORT StringFormatterFor<const wchar_t*> {static bool format(StringFormatOutput&, const StringSpan, const wchar_t*);};
267#endif
268template <> struct SC_COMPILER_EXPORT StringFormatterFor<StringSpan> {static bool format(StringFormatOutput&, const StringSpan, const StringSpan);};
269struct StringPath;
270template <> struct SC_COMPILER_EXPORT StringFormatterFor<StringPath> {static bool format(StringFormatOutput&, const StringSpan, const StringPath&);};
271
272// clang-format on
273
274template <int N>
275struct StringFormatterFor<char[N]>
276{
277 static bool format(StringFormatOutput& data, const StringView specifier, const char* str)
278 {
279 const StringView sv({str, N - 1}, true, StringEncoding::Ascii);
280 return StringFormatterFor<StringView>::format(data, specifier, sv);
281 }
282};
283
284} // namespace SC
constexpr const T & max(const T &t1, const T &t2)
Finds the maximum of two values.
Definition Compiler.h:285
unsigned short uint16_t
Platform independent (2) bytes unsigned int.
Definition PrimitiveTypes.h:37
constexpr T && forward(typename TypeTraits::RemoveReference< T >::type &value)
Forwards an lvalue or an rvalue as an rvalue reference.
Definition Compiler.h:260
unsigned char uint8_t
Platform independent (1) byte unsigned int.
Definition PrimitiveTypes.h:36
unsigned long size_t
Platform independent unsigned size type.
Definition PrimitiveTypes.h:56
unsigned long long uint64_t
Platform independent (8) bytes unsigned int.
Definition PrimitiveTypes.h:42
unsigned int uint32_t
Platform independent (4) bytes unsigned int.
Definition PrimitiveTypes.h:38
short int16_t
Platform independent (2) bytes signed int.
Definition PrimitiveTypes.h:45
long long int64_t
Platform independent (8) bytes signed int.
Definition PrimitiveTypes.h:50
signed char int8_t
Platform independent (1) byte signed int.
Definition PrimitiveTypes.h:44
signed long ssize_t
Platform independent signed size type.
Definition PrimitiveTypes.h:57
int int32_t
Platform independent (4) bytes signed int.
Definition PrimitiveTypes.h:46
uint32_t StringCodePoint
UTF code point (32 bit)
Definition StringIterator.h:14
A buffer of bytes with given alignment.
Definition AlignedStorage.h:29
Writes to console using SC::StringFormat.
Definition Console.h:25
Definition StringFormat.h:26
StringFormatOutput(StringEncoding encoding, T &destination)
Constructs a StringFormatOutput object pushing to a destination buffer.
Definition StringFormat.h:31
void onFormatBegin()
Method to be called when format begins, so that it can be rolled back on failure.
void onFormatFailed()
Method to be called when format fails (will rollback buffer to length before onFormatBegin)
StringFormatOutput(StringEncoding encoding, Console &destination)
Constructs a StringFormatOutput object pushing to a console.
bool onFormatSucceeded()
Method to be called when format succeeds.
bool append(StringView text)
Appends the StringView (eventually converting it) to destination buffer.
Formats String with a simple DSL embedded in the format string.
Definition StringFormat.h:100
static bool format(StringFormatOutput &data, StringView fmt, Types &&... args)
Formats fmt StringView using simple DSL where {} are replaced with args.
Definition StringFormat.h:224
An read-only view over a string (to avoid including Strings library when parsing is not needed).
Definition StringSpan.h:37
Non-owning view over a range of characters with UTF Encoding.
Definition StringView.h:46
constexpr StringIterator getIterator() const
Returns a StringIterator from current StringView.
Definition StringView.h:558
static StringView fromIteratorUntilEnd(StringIterator it, StringEncoding encoding=StringIterator::getEncoding())
Returns a section of a string, from it to end of StringView.
static StringView fromIterators(StringIterator from, StringIterator to, StringEncoding encoding=StringIterator::getEncoding())
Returns a StringView starting at from and ending at to.
EnableIf conditionally defines a type if a boolean template parameter is true.
Definition TypeTraits.h:25