microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
string.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 Jarosław Pelczar <jarek@jpelczar.com>
2//
3// SPDX-License-Identifier: BSD-2-Clause
4
5#pragma once
6
7// std-interop-file: opt-in formatter/conversions for std::basic_string, for interop with std::string APIs.
8
12#include "../microfmt.hpp"
13#include <cstddef>
14#include <string>
15#include <string_view>
16
17namespace microfmt {
18
26template <typename CharT, typename Traits, typename Alloc> struct formatter<std::basic_string<CharT, Traits, Alloc>> {
30 constexpr void parse(format_parse_context &) noexcept {}
36 void format(const std::basic_string<CharT, Traits, Alloc> &val, const sink &out) const noexcept {
37 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
38
39 out.write(std::basic_string_view<CharT, Traits>(val.data(), val.size()));
40
41 RELOCO_END_UNSAFE_BUFFER_USAGE;
42 }
43};
44
45namespace detail {
46
50enum class string_align : uint8_t { none, left, right, center };
51
59 char fill{' '};
63 string_align align{string_align::none};
67 size_t width{0};
71 size_t precision{size_t(-1)};
75 bool debug_escaped{false};
76};
77
84// low-level formatting: raw character pointers and integer width conversions bounded by the buffer sizes checked here.
85RELOCO_BEGIN_UNSAFE_CODE
86inline constexpr parsed_string_spec parse_advanced_string_spec(microfmt::string_view spec) noexcept {
88 if (spec.empty()) {
89 return res;
90 }
91
92 size_t pos = 0;
93 const size_t len = spec.size();
94
95 // Fill & Align: [[fill]align] (e.g. ">10", "*^10", "<5")
96 if (pos + 1 < len && (spec[pos + 1] == '<' || spec[pos + 1] == '>' || spec[pos + 1] == '^')) {
97 res.fill = spec[pos++];
98 const char a = spec[pos++];
99 if (a == '<')
100 res.align = string_align::left;
101 else if (a == '>')
102 res.align = string_align::right;
103 else if (a == '^')
104 res.align = string_align::center;
105 } else if (pos < len && (spec[pos] == '<' || spec[pos] == '>' || spec[pos] == '^')) {
106 const char a = spec[pos++];
107 if (a == '<')
108 res.align = string_align::left;
109 else if (a == '>')
110 res.align = string_align::right;
111 else if (a == '^')
112 res.align = string_align::center;
113 }
114
115 // Width: [width]
116 while (pos < len && spec[pos] >= '0' && spec[pos] <= '9') {
117 res.width = res.width * 10 + static_cast<size_t>(spec[pos++] - '0');
118 }
119
120 // Precision (truncation): [ .precision ]
121 if (pos < len && spec[pos] == '.') {
122 pos++;
123 res.precision = 0;
124 while (pos < len && spec[pos] >= '0' && spec[pos] <= '9') {
125 res.precision = res.precision * 10 + static_cast<size_t>(spec[pos++] - '0');
126 }
127 }
128
129 // Type / Mode: [s|?]
130 while (pos < len) {
131 if (spec[pos] == '?') {
132 res.debug_escaped = true;
133 }
134 pos++;
135 }
136
137 // Default to left align if width is requested without explicit alignment
138 if (res.width > 0 && res.align == string_align::none) {
139 res.align = string_align::left;
140 }
141
142 return res;
143}
145
152inline void write_fill_chars(const sink &out, char fill, size_t count) noexcept {
153 // NOLINTNEXTLINE(reloco-no-c-array): fixed-size buffer/table for span APIs
154 char buf[32] = {};
155 std::fill_n(buf, std::min<size_t>(count, sizeof(buf)), fill);
156 while (count > 0) {
157 const size_t chunk = std::min<size_t>(count, sizeof(buf));
158 out.write(microfmt::string_view(buf, chunk));
159 count -= chunk;
160 }
161}
162
168inline void write_escaped_character(const sink &out, char ch) noexcept {
169 switch (ch) {
170 case '\n':
171 out.write("\\n");
172 break;
173 case '\r':
174 out.write("\\r");
175 break;
176 case '\t':
177 out.write("\\t");
178 break;
179 case '\\':
180 out.write("\\\\");
181 break;
182 case '"':
183 out.write("\\\"");
184 break;
185 case '\0':
186 out.write("\\0");
187 break;
188 default:
189 if (static_cast<unsigned char>(ch) < 0x20 || static_cast<unsigned char>(ch) == 0x7F) {
190 static constexpr microfmt::string_view hex_chars = "0123456789abcdef";
191 // NOLINTNEXTLINE(reloco-no-c-array): fixed-size buffer/table for span APIs
192 char hex[4] = {'\\', 'x', hex_chars[(static_cast<unsigned char>(ch) >> 4) & 0x0F],
193 hex_chars[static_cast<unsigned char>(ch) & 0x0F]};
194 out.write(microfmt::string_view(hex, 4));
195 } else {
196 out.write(microfmt::string_view(&ch, 1));
197 }
198 break;
199 }
200}
201
207inline size_t calculate_escaped_len(microfmt::string_view sv) noexcept {
208 size_t len = 2; // Surrounding quotes
209 for (char ch : sv) {
210 switch (ch) {
211 case '\n':
212 case '\r':
213 case '\t':
214 case '\\':
215 case '"':
216 case '\0':
217 len += 2;
218 break;
219 default:
220 if (static_cast<unsigned char>(ch) < 0x20 || static_cast<unsigned char>(ch) == 0x7F) {
221 len += 4; // \xNN
222 } else {
223 len += 1;
224 }
225 break;
226 }
227 }
228 return len;
229}
230
231} // namespace detail
232
233// ============================================================================
234// as_string_view Wrapper Class
235// ============================================================================
236
240class MICROFMT_API_CLASS RELOCO_POINTER as_string_view {
241public:
246 constexpr explicit as_string_view(microfmt::string_view str) noexcept : str_(str) {}
247
252 [[nodiscard]] constexpr microfmt::string_view get() const & noexcept { return str_; }
253
254 [[nodiscard]] constexpr microfmt::string_view get() const && = delete;
255
256private:
258 microfmt::string_view str_;
259};
260
261// ============================================================================
262// Factory Functions: microfmt::as_string(...)
263// ============================================================================
264
270[[nodiscard]] constexpr as_string_view as_string(microfmt::string_view sv) noexcept { return as_string_view{sv}; }
271
280template <typename CharT, typename Traits, typename Alloc>
281[[nodiscard]] as_string_view as_string(const std::basic_string<CharT, Traits, Alloc> &str) noexcept {
282 return as_string_view{microfmt::string_view(str.data(), str.size())};
283}
284
285template <typename CharT, typename Traits, typename Alloc>
286[[nodiscard]] as_string_view as_string(std::basic_string<CharT, Traits, Alloc> &&) noexcept = delete;
287
293// low-level formatting: raw character pointers and integer width conversions bounded by the buffer sizes checked here.
294RELOCO_BEGIN_UNSAFE_CODE
295[[nodiscard]] constexpr as_string_view as_string(const char *str) noexcept {
296 return as_string_view{str != nullptr ? microfmt::string_view(str) : microfmt::string_view("(null)")};
297}
298RELOCO_END_UNSAFE_CODE
299
300// ============================================================================
301// Formatter for as_string_view
302// ============================================================================
303
308template <> struct formatter<as_string_view> {
313
318 constexpr void parse(format_parse_context &ctx) noexcept { spec_ = detail::parse_advanced_string_spec(ctx.spec()); }
319
325 void format(const as_string_view &val, const sink &out) const noexcept {
326 microfmt::string_view sv = val.get();
327
328 // 1. Truncate to precision if requested
329 if (spec_.precision != size_t(-1) && sv.size() > spec_.precision) {
330 sv = sv.substr(0, spec_.precision);
331 }
332
333 // 2. Direct render path if debug escaping and padding are unused
334 if (!spec_.debug_escaped && spec_.align == detail::string_align::none && spec_.width <= sv.size()) {
335 out.write(sv);
336 return;
337 }
338
339 const size_t visible_len = spec_.debug_escaped ? detail::calculate_escaped_len(sv) : sv.size();
340 const size_t total_pad = (spec_.width > visible_len) ? (spec_.width - visible_len) : 0;
341
342 const auto emit_body = [this, &out, &sv]() {
343 if (spec_.debug_escaped) {
344 out.write("\"");
345 for (char ch : sv) {
346 detail::write_escaped_character(out, ch);
347 }
348 out.write("\"");
349 } else {
350 out.write(sv);
351 }
352 };
353
354 if (total_pad == 0) {
355 emit_body();
356 return;
357 }
358
359 // 3. Padded Output
360 switch (spec_.align) {
361 case detail::string_align::left:
362 case detail::string_align::none:
363 emit_body();
364 detail::write_fill_chars(out, spec_.fill, total_pad);
365 break;
366
367 case detail::string_align::right:
368 detail::write_fill_chars(out, spec_.fill, total_pad);
369 emit_body();
370 break;
371
372 case detail::string_align::center: {
373 const size_t left_pad = total_pad / 2;
374 const size_t right_pad = total_pad - left_pad;
375 detail::write_fill_chars(out, spec_.fill, left_pad);
376 emit_body();
377 detail::write_fill_chars(out, spec_.fill, right_pad);
378 break;
379 }
380 }
381 }
382};
383
384} // namespace microfmt
Non-owning string view supporting advanced format specifiers.
Definition string.hpp:240
constexpr microfmt::string_view get() const &&=delete
constexpr microfmt::string_view get() const &noexcept
Returns the underlying string view.
Definition string.hpp:252
constexpr as_string_view(microfmt::string_view str) noexcept
Constructs the view over a string_view.
Definition string.hpp:246
Definition microfmt.hpp:881
#define MICROFMT_API_CLASS
Definition compat.hpp:181
RELOCO_END_UNSAFE_CODE void write_fill_chars(const sink &out, char fill, size_t count) noexcept
Writes count copies of fill to the sink in bounded chunks.
Definition string.hpp:152
string_align
Horizontal alignment mode for padded string output.
Definition string.hpp:50
RELOCO_BEGIN_UNSAFE_CODE constexpr parsed_string_spec parse_advanced_string_spec(microfmt::string_view spec) noexcept
Parses an advanced string specifier: [[fill]align][width][.precision][?].
Definition string.hpp:86
size_t calculate_escaped_len(microfmt::string_view sv) noexcept
Computes the rendered character count of a debug-escaped string.
Definition string.hpp:207
void write_escaped_character(const sink &out, char ch) noexcept
Writes a single character with C-style debug escaping.
Definition string.hpp:168
Definition assert.hpp:19
constexpr as_string_view as_string(microfmt::string_view sv) noexcept
Wraps a string_view for advanced string formatting.
Definition string.hpp:270
constexpr hex_view< T > hex(T val, int width=0, bool prefix=false, bool uppercase=false) noexcept
Definition format_helpers.hpp:29
reloco::sink sink
Type-erased, zero-allocation output sink.
Definition microfmt.hpp:46
RELOCO_END_UNSAFE_CODE
Definition remote_object.hpp:132
Parsed advanced string format specifier state.
Definition string.hpp:55
char fill
Fill character used for padding (default space).
Definition string.hpp:59
constexpr void parse(format_parse_context &ctx) noexcept
Parses the advanced string specifier.
Definition string.hpp:318
void format(const as_string_view &val, const sink &out) const noexcept
Renders the string with the requested alignment/padding/escaping.
Definition string.hpp:325
void format(const std::basic_string< CharT, Traits, Alloc > &val, const sink &out) const noexcept
Writes the string contents verbatim.
Definition string.hpp:36
constexpr void parse(format_parse_context &) noexcept
No-op parse; plain strings accept no format specifier.
Definition string.hpp:30
Definition microfmt.hpp:985