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 "../Common/AlignedStorage.h"
5#include "../Common/CompilerMinMax.h"
6#include "../Common/IGrowableBufferSpan.h"
7#include "../Strings/StringView.h"
8#include "StringsExport.h"
9
10namespace SC
11{
12struct Console;
13
14struct StringFormatOutput;
15template <typename T>
16struct StringFormatterFor
17{
18 static bool format(StringFormatOutput& data, const StringSpan specifier, const T& value)
19 {
20 return StringFormatterFor<decltype(value.view())>::format(data, specifier, value.view());
21 };
22};
23
27struct SC_STRINGS_EXPORT StringFormatOutput
28{
29 StringFormatOutput(StringEncoding encoding, IGrowableBuffer& buffer) : encoding(encoding), growableBuffer(&buffer)
30 {}
31
36 StringFormatOutput(StringEncoding encoding, Console& destination, bool useStdOut);
37
41 [[nodiscard]] bool append(StringView text);
42
45
48
51 [[nodiscard]] bool onFormatSucceeded();
52
53 private:
54 IGrowableBuffer* growableBuffer = nullptr;
55 StringEncoding encoding;
56 bool useStdOut = true;
57
58 Console* console = nullptr;
59 size_t backupSize = 0;
60};
61
83template <typename RangeIterator>
85{
92 template <typename... Types>
93 [[nodiscard]] static bool format(StringFormatOutput& data, StringView fmt, Types&&... args);
94
95 private:
96 struct Implementation;
97};
99
100} // namespace SC
101
102//-----------------------------------------------------------------------------------------------------------------------
103// Implementations Details
104//-----------------------------------------------------------------------------------------------------------------------
105template <typename RangeIterator>
106struct SC::StringFormat<RangeIterator>::Implementation
107{
108 template <int Total, int N, typename T, typename... Rest>
109 static bool formatArgument(StringFormatOutput& data, StringView specifier, int position, T&& arg, Rest&&... rest)
110 {
111 if (position == Total - N)
112 {
113 using First = typename TypeTraits::RemoveConst<typename TypeTraits::RemoveReference<T>::type>::type;
114 return StringFormatterFor<First>::format(data, specifier, arg);
115 }
116 else
117 {
118 return formatArgument<Total, N - 1>(data, specifier, position, forward<Rest>(rest)...);
119 }
120 }
121
122 template <int Total, int N, typename... Args>
123 static typename SC::TypeTraits::EnableIf<sizeof...(Args) == 0, bool>::type formatArgument(StringFormatOutput&,
124 StringView, int, Args...)
125 {
126 return false;
127 }
128
129 template <typename... Types>
130 static bool parsePosition(StringFormatOutput& data, RangeIterator& it, int32_t& parsedPosition, Types&&... args)
131 {
132 const auto startOfSpecifier = it;
133 if (it.advanceUntilMatches('}')) // We have an already matched '{' when arriving here
134 {
135 auto specifier = startOfSpecifier.sliceFromStartUntil(it);
136 auto specifierPosition = specifier;
137 if (specifier.advanceUntilMatches(':'))
138 {
139 specifierPosition = startOfSpecifier.sliceFromStartUntil(specifier);
140 (void)specifier.stepForward(); // eat '{'
141 }
142 (void)specifierPosition.stepForward(); // eat '{'
143 (void)it.stepForward(); // eat '}'
144 const StringView positionString = StringView::fromIteratorUntilEnd(specifierPosition);
145 const StringView specifierString = StringView::fromIteratorUntilEnd(specifier);
146 if (not positionString.isEmpty())
147 {
148 if (not positionString.parseInt32(parsedPosition))
149 {
150 return false;
151 }
152 }
153 constexpr auto maxArgs = sizeof...(args);
154 return formatArgument<maxArgs, maxArgs>(data, specifierString, parsedPosition, forward<Types>(args)...);
155 }
156 return false;
157 }
158
159 template <typename... Types>
160 static bool executeFormat(StringFormatOutput& data, RangeIterator it, Types&&... args)
161 {
162 StringCodePoint matchedChar;
163
164 auto start = it;
165 int32_t position = 0;
166 int32_t maxPosition = 0;
167 while (true)
168 {
169 if (it.advanceUntilMatchesAny({'{', '}'}, matchedChar)) // match start or end of specifier
170 {
171 if (it.isFollowedBy(matchedChar))
172 SC_LANGUAGE_UNLIKELY // if it's the same matched, let's escape it
173 {
174 (void)it.stepForward(); // we want to make sure we insert the escaped '{' or '}'
175 if (not data.append(StringView::fromIterators(start, it)))
176 return false;
177 (void)it.stepForward(); // we don't want to insert the additional '{' or '}' needed for escaping
178 start = it;
179 }
180 else if (matchedChar == '{') // it's a '{' not followed by itself, so let's parse specifier
181 {
182 if (not data.append(StringView::fromIterators(start, it))) // write everything before '{
183 return false;
184 // try parse '}' and eventually format
185 int32_t parsedPosition = position;
186 if (not parsePosition(data, it, parsedPosition, forward<Types>(args)...))
187 return false;
188 start = it;
189 position += 1;
190 maxPosition = max(maxPosition, parsedPosition + 1);
191 }
192 else
193 {
194 return false; // arriving here means end of string with as single, unescaped '}'
195 }
196 }
197 else
198 {
199 if (not data.append(StringView::fromIterators(start, it))) // write everything before '{
200 return false;
201 return maxPosition == static_cast<int32_t>(sizeof...(args)); // check right number of args
202 }
203 }
204 }
205};
206
207template <typename RangeIterator>
208template <typename... Types>
210{
211 SC_TRY(fmt.getEncoding() != StringEncoding::Utf16); // UTF16 format strings are not supported
212 data.onFormatBegin();
213 if (Implementation::executeFormat(data, fmt.getIterator<RangeIterator>(), forward<Types>(args)...))
214 SC_LANGUAGE_LIKELY { return data.onFormatSucceeded(); }
215 else
216 {
217 data.onFormatFailed();
218 return false;
219 }
220}
221
222namespace SC
223{
224// clang-format off
225template <> struct SC_STRINGS_EXPORT StringFormatterFor<float> {static bool format(StringFormatOutput&, const StringSpan, const float);};
226template <> struct SC_STRINGS_EXPORT StringFormatterFor<double> {static bool format(StringFormatOutput&, const StringSpan, const double);};
227#if SC_COMPILER_MSVC || SC_COMPILER_CLANG_CL
228#if SC_PLATFORM_64_BIT == 0
229template <> struct SC_STRINGS_EXPORT StringFormatterFor<SC::ssize_t> {static bool format(StringFormatOutput&, const StringSpan, const SC::ssize_t);};
230#endif
231#else
232#if !SC_PLATFORM_LINUX && !(SC_PLATFORM_WINDOWS && SC_PLATFORM_64_BIT)
233template <> struct SC_STRINGS_EXPORT StringFormatterFor<SC::size_t> {static bool format(StringFormatOutput&, const StringSpan, const SC::size_t);};
234template <> struct SC_STRINGS_EXPORT StringFormatterFor<SC::ssize_t> {static bool format(StringFormatOutput&, const StringSpan, const SC::ssize_t);};
235#endif
236#endif
237template <> struct SC_STRINGS_EXPORT StringFormatterFor<SC::int64_t> {static bool format(StringFormatOutput&, const StringSpan, const SC::int64_t);};
238template <> struct SC_STRINGS_EXPORT StringFormatterFor<SC::uint64_t> {static bool format(StringFormatOutput&, const StringSpan, const SC::uint64_t);};
239template <> struct SC_STRINGS_EXPORT StringFormatterFor<SC::int32_t> {static bool format(StringFormatOutput&, const StringSpan, const SC::int32_t);};
240template <> struct SC_STRINGS_EXPORT StringFormatterFor<SC::uint32_t> {static bool format(StringFormatOutput&, const StringSpan, const SC::uint32_t);};
241template <> struct SC_STRINGS_EXPORT StringFormatterFor<SC::int16_t> {static bool format(StringFormatOutput&, const StringSpan, const SC::int16_t);};
242template <> struct SC_STRINGS_EXPORT StringFormatterFor<SC::uint16_t> {static bool format(StringFormatOutput&, const StringSpan, const SC::uint16_t);};
243template <> struct SC_STRINGS_EXPORT StringFormatterFor<SC::int8_t> {static bool format(StringFormatOutput&, const StringSpan, const SC::int8_t);};
244template <> struct SC_STRINGS_EXPORT StringFormatterFor<SC::uint8_t> {static bool format(StringFormatOutput&, const StringSpan, const SC::uint8_t);};
245template <> struct SC_STRINGS_EXPORT StringFormatterFor<char> {static bool format(StringFormatOutput&, const StringSpan, const char);};
246template <> struct SC_STRINGS_EXPORT StringFormatterFor<bool> {static bool format(StringFormatOutput&, const StringSpan, const bool);};
247template <> struct SC_STRINGS_EXPORT StringFormatterFor<StringView> {static bool format(StringFormatOutput&, const StringSpan, const StringView);};
248template <> struct SC_STRINGS_EXPORT StringFormatterFor<const char*> {static bool format(StringFormatOutput&, const StringSpan, const char*);};
249template <> struct SC_STRINGS_EXPORT StringFormatterFor<const void*> {static bool format(StringFormatOutput&, const StringSpan, const void*);};
250#if SC_PLATFORM_WINDOWS
251template <> struct SC_STRINGS_EXPORT StringFormatterFor<wchar_t> {static bool format(StringFormatOutput&, const StringSpan, const wchar_t);};
252template <> struct SC_STRINGS_EXPORT StringFormatterFor<const wchar_t*> {static bool format(StringFormatOutput&, const StringSpan, const wchar_t*);};
253#endif
254template <> struct SC_STRINGS_EXPORT StringFormatterFor<StringSpan> {static bool format(StringFormatOutput&, const StringSpan, const StringSpan);};
255struct StringPath;
256template <> struct SC_STRINGS_EXPORT StringFormatterFor<StringPath> {static bool format(StringFormatOutput&, const StringSpan, const StringPath&);};
257
258// clang-format on
259
260template <int N>
261struct StringFormatterFor<char[N]>
262{
263 static bool format(StringFormatOutput& data, const StringView specifier, const char* str)
264 {
265 const StringView sv({str, N - 1}, true, StringEncoding::Ascii);
266 return StringFormatterFor<StringView>::format(data, specifier, sv);
267 }
268};
269
270} // namespace SC
uint32_t StringCodePoint
UTF code point (32 bit)
Definition StringIterator.h:14
Writes to console using SC::StringFormat.
Definition Console.h:26
Definition StringFormat.h:28
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, bool useStdOut)
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:85
static bool format(StringFormatOutput &data, StringView fmt, Types &&... args)
Formats fmt StringView using simple DSL where {} are replaced with args.
Definition StringFormat.h:209
Non-owning view over a range of characters with UTF Encoding.
Definition StringView.h:49
constexpr StringIterator getIterator() const
Returns a StringIterator from current StringView.
Definition StringView.h:573
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.