FastLED 3.9.15
Loading...
Searching...
No Matches
format.h
Go to the documentation of this file.
1#pragma once
2
40
41#include "fl/stl/string.h"
42#include "fl/stl/charconv.h"
43#include "fl/stl/bit_cast.h"
44#include "fl/stl/type_traits.h"
45#include "fl/stl/int.h"
46#include "fl/stl/noexcept.h"
47
48namespace fl {
49
50namespace format_detail {
51
52// Format specification parsed from {:...}
53struct FormatSpec {
54 char fill = ' '; // Fill character for alignment
55 char align = '\0'; // '<' left, '>' right, '^' center, '\0' default
56 char sign = '-'; // '+' always, '-' negative only, ' ' space for positive
57 bool alternate = false; // '#' alternate form (0x, 0b, etc.)
58 bool zero_pad = false; // '0' zero padding
59 int width = 0; // Minimum field width
60 int precision = -1; // Precision for floats (-1 = default)
61 char type = '\0'; // 'd', 'x', 'X', 'b', 'o', 'f', 's', 'c', etc.
62};
63
64// Parse a format specification from the string after ':'
65// Returns pointer past the parsed spec (should be at '}')
66inline const char* parse_format_spec(const char* p, FormatSpec& spec) {
67 if (!p || *p == '}') return p;
68
69 // Check for fill and align (fill char followed by align char)
70 // Align chars are: < > ^
71 if (p[0] && p[1] && (p[1] == '<' || p[1] == '>' || p[1] == '^')) {
72 spec.fill = p[0];
73 spec.align = p[1];
74 p += 2;
75 } else if (*p == '<' || *p == '>' || *p == '^') {
76 spec.align = *p++;
77 }
78
79 // Sign: + - or space
80 if (*p == '+' || *p == '-' || *p == ' ') {
81 spec.sign = *p++;
82 }
83
84 // Alternate form: #
85 if (*p == '#') {
86 spec.alternate = true;
87 ++p;
88 }
89
90 // Zero padding: 0 (only if no alignment specified)
91 if (*p == '0' && spec.align == '\0') {
92 spec.zero_pad = true;
93 ++p;
94 }
95
96 // Width
97 while (*p >= '0' && *p <= '9') {
98 spec.width = spec.width * 10 + (*p - '0');
99 ++p;
100 }
101
102 // Precision: .N
103 if (*p == '.') {
104 ++p;
105 spec.precision = 0;
106 while (*p >= '0' && *p <= '9') {
107 spec.precision = spec.precision * 10 + (*p - '0');
108 ++p;
109 }
110 }
111
112 // Type specifier
113 if (*p && *p != '}') {
114 spec.type = *p++;
115 }
116
117 return p;
118}
119
120// Apply width/alignment to a formatted string
121inline void apply_width_align(fl::string& result, const fl::string& value, const FormatSpec& spec) {
122 fl::size value_len = value.size();
123
124 if (spec.width <= 0 || static_cast<fl::size>(spec.width) <= value_len) {
125 result.append(value);
126 return;
127 }
128
129 fl::size padding = static_cast<fl::size>(spec.width) - value_len;
130 char fill = spec.fill;
131
132 char align = spec.align;
133 if (align == '\0') {
134 align = '>'; // Default right-align for numbers
135 }
136
137 if (align == '<') {
138 // Left align: value then padding
139 result.append(value);
140 for (fl::size i = 0; i < padding; ++i) {
141 result.append(fill);
142 }
143 } else if (align == '>') {
144 // Right align: padding then value
145 for (fl::size i = 0; i < padding; ++i) {
146 result.append(fill);
147 }
148 result.append(value);
149 } else if (align == '^') {
150 // Center align
151 fl::size left_pad = padding / 2;
152 fl::size right_pad = padding - left_pad;
153 for (fl::size i = 0; i < left_pad; ++i) {
154 result.append(fill);
155 }
156 result.append(value);
157 for (fl::size i = 0; i < right_pad; ++i) {
158 result.append(fill);
159 }
160 }
161}
162
163// Format an integer value
164template <typename T>
166 char buf[68]; // Enough for 64-bit binary + prefix
167 char* p = buf + sizeof(buf);
168 *--p = '\0';
169
170 bool negative = false;
171 using UnsignedT = typename fl::make_unsigned<T>::type;
172 UnsignedT uval;
173
174 if (fl::is_signed<T>::value && value < 0) {
175 negative = true;
176 uval = static_cast<UnsignedT>(-(value + 1)) + 1; // Safe negation
177 } else {
178 uval = static_cast<UnsignedT>(value);
179 }
180
181 char type = spec.type ? spec.type : 'd';
182 int base = 10;
183 const char* digits = "0123456789abcdef";
184
185 switch (type) {
186 case 'b': case 'B': base = 2; break;
187 case 'o': base = 8; break;
188 case 'x': base = 16; digits = "0123456789abcdef"; break;
189 case 'X': base = 16; digits = "0123456789ABCDEF"; break;
190 case 'c': {
191 // Character output
192 char ch = static_cast<char>(value);
193 return fl::string(&ch, 1);
194 }
195 default: base = 10; break;
196 }
197
198 // Convert to string (in reverse)
199 if (uval == 0) {
200 *--p = '0';
201 } else {
202 while (uval > 0) {
203 *--p = digits[uval % base];
204 uval /= base;
205 }
206 }
207
208 // Build prefix
209 fl::string prefix;
210 if (negative) {
211 prefix.append('-');
212 } else if (spec.sign == '+') {
213 prefix.append('+');
214 } else if (spec.sign == ' ') {
215 prefix.append(' ');
216 }
217
218 if (spec.alternate && base != 10) {
219 if (base == 16) {
220 prefix.append('0');
221 prefix.append('x'); // Always lowercase 'x' in prefix (digits control case)
222 } else if (base == 2) {
223 prefix.append('0');
224 prefix.append('b'); // Always lowercase 'b' in prefix
225 } else if (base == 8 && *p != '0') {
226 prefix.append('0');
227 }
228 }
229
230 fl::string num_str(p);
231
232 // Handle zero padding
233 if (spec.zero_pad && spec.width > 0) {
234 int num_width = spec.width - static_cast<int>(prefix.size());
235 if (num_width > static_cast<int>(num_str.size())) {
236 fl::string padded;
237 for (int i = 0; i < num_width - static_cast<int>(num_str.size()); ++i) {
238 padded.append('0');
239 }
240 padded.append(num_str);
241 return fl::string(prefix) += padded;
242 }
243 }
244
245 return fl::string(prefix) += num_str;
246}
247
248// Format a floating point value
249inline fl::string format_float(double value, const FormatSpec& spec) {
250 int precision = spec.precision >= 0 ? spec.precision : 6;
251
253
254 // Handle sign
255 if (value < 0) {
256 result.append('-');
257 value = -value;
258 } else if (spec.sign == '+') {
259 result.append('+');
260 } else if (spec.sign == ' ') {
261 result.append(' ');
262 }
263
264 // Format the number
265 char buf[64];
266 fl::ftoa(static_cast<float>(value), buf, precision);
267 result.append(buf);
268
269 return result;
270}
271
272// Format a pointer
273inline fl::string format_pointer(const void* ptr, const FormatSpec& spec) {
274 fl::uptr addr = fl::ptr_to_int(const_cast<void*>(ptr));
275
276 FormatSpec hex_spec = spec;
277 hex_spec.type = 'x';
278 hex_spec.alternate = true;
279
280 return format_integer(addr, hex_spec);
281}
282
283// Format a string value
284inline fl::string format_string(const char* value, const FormatSpec& spec) {
285 if (!value) value = "(null)";
286
287 fl::string str(value);
288
289 // Apply precision as max length for strings
290 if (spec.precision >= 0 && static_cast<fl::size>(spec.precision) < str.size()) {
291 str = fl::string(value, static_cast<fl::size>(spec.precision));
292 }
293
294 return str;
295}
296
297// Type-erased argument holder
299public:
302
304 FormatArg(int v) : mType(Type::Int) { mData.i = v; }
305 FormatArg(unsigned int v) : mType(Type::UInt) { mData.u = v; }
306 FormatArg(long v) : mType(Type::Long) { mData.l = v; }
307 FormatArg(unsigned long v) : mType(Type::ULong) { mData.ul = v; }
308 FormatArg(long long v) : mType(Type::LongLong) { mData.ll = v; }
309 FormatArg(unsigned long long v) : mType(Type::ULongLong) { mData.ull = v; }
310 FormatArg(double v) : mType(Type::Double) { mData.d = v; }
311 FormatArg(float v) : mType(Type::Double) { mData.d = v; }
312 FormatArg(char v) : mType(Type::Char) { mData.c = v; }
313 FormatArg(const char* v) : mType(Type::CString) { mData.s = v; }
314 FormatArg(const fl::string& v) : mType(Type::String) { mData.str = &v; }
315 FormatArg(const void* v) : mType(Type::Pointer) { mData.p = v; }
316
317 // Short/byte types promote to int
318 FormatArg(short v) : mType(Type::Int) { mData.i = v; }
319 FormatArg(unsigned short v) : mType(Type::UInt) { mData.u = v; }
320 FormatArg(signed char v) : mType(Type::Int) { mData.i = v; }
321 FormatArg(unsigned char v) : mType(Type::UInt) { mData.u = v; }
322
323 fl::string format(const FormatSpec& spec) const {
324 switch (mType) {
325 case Type::Int: return format_integer(mData.i, spec);
326 case Type::UInt: return format_integer(mData.u, spec);
327 case Type::Long: return format_integer(mData.l, spec);
328 case Type::ULong: return format_integer(mData.ul, spec);
329 case Type::LongLong: return format_integer(mData.ll, spec);
330 case Type::ULongLong: return format_integer(mData.ull, spec);
331 case Type::Double: return format_float(mData.d, spec);
332 case Type::Char: {
333 if (spec.type == 'd' || spec.type == 'x' || spec.type == 'X' ||
334 spec.type == 'b' || spec.type == 'o') {
335 return format_integer(static_cast<int>(mData.c), spec);
336 }
337 char buf[2] = {mData.c, '\0'};
338 return fl::string(buf);
339 }
340 case Type::CString: return format_string(mData.s, spec);
341 case Type::String: return format_string(mData.str->c_str(), spec);
342 case Type::Pointer: return format_pointer(mData.p, spec);
343 case Type::None:
344 default: return fl::string("<invalid>");
345 }
346 }
347
348 bool valid() const { return mType != Type::None; }
349
350private:
352 union {
353 int i;
354 unsigned int u;
355 long l;
356 unsigned long ul;
357 long long ll;
358 unsigned long long ull;
359 double d;
360 char c;
361 const char* s;
362 const fl::string* str;
363 const void* p;
365};
366
367// Core formatting implementation
368inline fl::string format_impl(const char* fmt, const FormatArg* args, int num_args) {
370 const char* p = fmt;
371 int auto_index = 0;
372
373 while (*p) {
374 if (*p == '{') {
375 if (*(p + 1) == '{') {
376 // Escaped {{
377 result.append('{');
378 p += 2;
379 continue;
380 }
381
382 ++p; // Skip '{'
383
384 // Parse argument index
385 int arg_index = -1;
386 if (*p >= '0' && *p <= '9') {
387 arg_index = 0;
388 while (*p >= '0' && *p <= '9') {
389 arg_index = arg_index * 10 + (*p - '0');
390 ++p;
391 }
392 } else {
393 arg_index = auto_index++;
394 }
395
396 // Parse format spec
397 FormatSpec spec;
398 if (*p == ':') {
399 ++p;
400 p = parse_format_spec(p, spec);
401 }
402
403 // Skip to '}'
404 while (*p && *p != '}') ++p;
405 if (*p == '}') ++p;
406
407 // Format the argument
408 if (arg_index >= 0 && arg_index < num_args) {
409 fl::string formatted = args[arg_index].format(spec);
410 apply_width_align(result, formatted, spec);
411 } else {
412 result.append("<out_of_range>");
413 }
414 } else if (*p == '}') {
415 if (*(p + 1) == '}') {
416 // Escaped }}
417 result.append('}');
418 p += 2;
419 } else {
420 result.append(*p++);
421 }
422 } else {
423 // Regular character - find next special char
424 const char* next = p;
425 while (*next && *next != '{' && *next != '}') {
426 ++next;
427 }
428 result.append(p, static_cast<fl::size>(next - p));
429 p = next;
430 }
431 }
432
433 return result;
434}
435
436} // namespace format_detail
437
439inline fl::string format(const char* fmt) {
440 return format_detail::format_impl(fmt, nullptr, 0);
441}
442
444template <typename... Args>
445fl::string format(const char* fmt, const Args&... args) {
447 return format_detail::format_impl(fmt, arg_array, static_cast<int>(sizeof...(Args)));
448}
449
450} // namespace fl
fl::size size() const FL_NOEXCEPT
union fl::format_detail::FormatArg::@343262056374325343016100266356354013163234001243 mData
FormatArg() FL_NOEXCEPT
Definition format.h:303
FormatArg(const fl::string &v)
Definition format.h:314
FormatArg(signed char v)
Definition format.h:320
FormatArg(unsigned long v)
Definition format.h:307
FormatArg(unsigned int v)
Definition format.h:305
FormatArg(unsigned long long v)
Definition format.h:309
FormatArg(const void *v)
Definition format.h:315
FormatArg(unsigned char v)
Definition format.h:321
FormatArg(const char *v)
Definition format.h:313
FormatArg(unsigned short v)
Definition format.h:319
fl::string format(const FormatSpec &spec) const
Definition format.h:323
string & append(const bitset_fixed< N > &bs) FL_NOEXCEPT
Definition string.h:284
fl::string format_integer(T value, const FormatSpec &spec)
Definition format.h:165
fl::string format_pointer(const void *ptr, const FormatSpec &spec)
Definition format.h:273
void apply_width_align(fl::string &result, const fl::string &value, const FormatSpec &spec)
Definition format.h:121
fl::string format_string(const char *value, const FormatSpec &spec)
Definition format.h:284
fl::string format_impl(const char *fmt, const FormatArg *args, int num_args)
Definition format.h:368
fl::string format_float(double value, const FormatSpec &spec)
Definition format.h:249
const char * parse_format_spec(const char *p, FormatSpec &spec)
Definition format.h:66
constexpr int type_rank< T >::value
uptr ptr_to_int(T *ptr) FL_NOEXCEPT
Definition bit_cast.h:71
expected< T, E > result
Alias for expected (Rust-style naming)
Definition result.h:31
constexpr int numeric_limits< char >::digits
void fill(Iterator first, Iterator last, const T &value) FL_NOEXCEPT
Definition algorithm.h:204
void ftoa(float value, char *buffer, int precision)
Convert floating point number to string buffer.
fl::string format(const char *fmt)
Format with no arguments.
Definition format.h:439
Base definition for an LED controller.
Definition crgb.hpp:179
corkscrew_args args
Definition old.h:149
#define FL_NOEXCEPT