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