FastLED 3.9.15
Loading...
Searching...
No Matches
stdio.h
Go to the documentation of this file.
1#pragma once
2
3#include "fl/stl/int.h"
4#include "fl/stl/cstddef.h"
5#include "fl/stl/cstdio.h" // For fl::print and fl::println
6#include "fl/stl/string.h" // For fl::string, to_hex
7#include "fl/stl/strstream.h"
9#include "fl/stl/noexcept.h"
10
11namespace fl {
12
36template<typename... Args>
37void printf(const char* format, const Args&... args) FL_NOEXCEPT;
38
63template<typename... Args>
64int snprintf(char* buffer, fl::size size, const char* format, const Args&... args) FL_NOEXCEPT;
65
81template<fl::size N, typename... Args>
82int sprintf(char (&buffer)[N], const char* format, const Args&... args) FL_NOEXCEPT;
83
84
86
87namespace printf_detail {
88
89// Helper to parse format specifiers and extract precision
90struct FormatSpec {
91 char type = '\0'; // Format character (d, f, s, etc.)
92 int precision = -1; // Precision for floating point
93 int width = 0; // Minimum field width
94 bool uppercase = false; // For hex formatting
95 bool left_align = false; // '-' flag: left-align
96 bool zero_pad = false; // '0' flag: zero-padding
97 bool show_sign = false; // '+' flag: always show sign
98 bool space_sign = false; // ' ' flag: space for positive
99 bool alt_form = false; // '#' flag: alternate form (0x, 0)
100
101 FormatSpec() = default;
102 explicit FormatSpec(char t) FL_NOEXCEPT : type(t) {}
103 FormatSpec(char t, int prec) FL_NOEXCEPT : type(t), precision(prec) {}
104};
105
106// Parse a format specifier from the format string
107// Returns the format spec and advances the pointer past the specifier
108// Format: %[flags][width][.precision][length]type
110 FormatSpec spec;
111
112 if (*format != '%') {
113 return spec;
114 }
115
116 ++format; // Skip the '%'
117
118 // Handle literal '%'
119 if (*format == '%') {
120 spec.type = '%';
121 ++format;
122 return spec;
123 }
124
125 // Parse flags: -, +, space, #, 0 (can be in any order)
126 bool parsing_flags = true;
127 while (parsing_flags) {
128 switch (*format) {
129 case '-':
130 spec.left_align = true;
131 ++format;
132 break;
133 case '+':
134 spec.show_sign = true;
135 ++format;
136 break;
137 case ' ':
138 spec.space_sign = true;
139 ++format;
140 break;
141 case '#':
142 spec.alt_form = true;
143 ++format;
144 break;
145 case '0':
146 spec.zero_pad = true;
147 ++format;
148 break;
149 default:
150 parsing_flags = false;
151 break;
152 }
153 }
154
155 // Parse width (decimal number)
156 if (*format >= '0' && *format <= '9') {
157 spec.width = 0;
158 while (*format >= '0' && *format <= '9') {
159 spec.width = spec.width * 10 + (*format - '0');
160 ++format;
161 }
162 }
163
164 // Parse precision for floating point
165 if (*format == '.') {
166 ++format; // Skip the '.'
167 spec.precision = 0;
168 while (*format >= '0' && *format <= '9') {
169 spec.precision = spec.precision * 10 + (*format - '0');
170 ++format;
171 }
172 }
173
174 // Skip length modifiers (l, ll, h, hh, L, z, t, j)
175 // We don't use them in our simple printf, but we need to skip them
176 // to get to the actual type character
177 if (*format == 'h' || *format == 'l') {
178 char first = *format;
179 ++format;
180 // Check for double character modifiers (hh, ll)
181 if (*format == first) {
182 ++format;
183 }
184 } else if (*format == 'L' || *format == 'z' || *format == 't' || *format == 'j') {
185 ++format;
186 }
187
188 // Get the format type
189 spec.type = *format;
190 if (spec.type == 'X') {
191 spec.uppercase = true;
192 spec.type = 'x'; // Normalize to lowercase for processing
193 }
194
195 ++format;
196 return spec;
197}
198
199// Convert unsigned integer to octal string
200template<typename T>
202 if (value == 0) {
203 return "0";
204 }
205
206 char buffer[32]; // Enough for 64-bit octal
207 int pos = 31;
208 buffer[pos] = '\0';
209
210 unsigned long long val = static_cast<unsigned long long>(value);
211 while (val > 0) {
212 buffer[--pos] = '0' + (val & 7);
213 val >>= 3;
214 }
215
216 return fl::string(&buffer[pos]);
217}
218
219// Apply width and padding to a string based on format spec
220inline fl::string apply_width(const fl::string& str, const FormatSpec& spec, bool is_numeric = false) FL_NOEXCEPT {
221 int len = static_cast<int>(str.length());
222
223 // No width specified or content already wider
224 if (spec.width <= len) {
225 return str;
226 }
227
228 int padding = spec.width - len;
229 char pad_char = ' ';
230
231 // Zero-padding only for numeric types and right-align
232 if (spec.zero_pad && is_numeric && !spec.left_align) {
233 pad_char = '0';
234
235 // Handle sign for zero-padding: move sign to front
236 if (!str.empty() && (str[0] == '-' || str[0] == '+' || str[0] == ' ')) {
238 result += str[0]; // Sign first
239 for (int i = 0; i < padding; ++i) {
240 result += pad_char;
241 }
242 for (size_t i = 1; i < str.length(); ++i) {
243 result += str[i];
244 }
245 return result;
246 }
247
248 // Handle 0x prefix for zero-padding
249 if (str.length() >= 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) {
251 result += str[0]; // '0'
252 result += str[1]; // 'x' or 'X'
253 for (int i = 0; i < padding; ++i) {
254 result += pad_char;
255 }
256 for (size_t i = 2; i < str.length(); ++i) {
257 result += str[i];
258 }
259 return result;
260 }
261 }
262
264 if (spec.left_align) {
265 // Left-align: content then padding
266 result = str;
267 for (int i = 0; i < padding; ++i) {
268 result += pad_char;
269 }
270 } else {
271 // Right-align: padding then content
272 for (int i = 0; i < padding; ++i) {
273 result += pad_char;
274 }
275 result += str;
276 }
277
278 return result;
279}
280
281// Format floating point with specified precision
282inline fl::string format_float(float value, int precision) FL_NOEXCEPT {
283 if (precision < 0) {
284 // Default precision - use sstream's default behavior
285 sstream stream;
286 stream << value;
287 return stream.str();
288 }
289
290 // Simple precision formatting
291 // This is a basic implementation - could be enhanced
292 if (precision == 0) {
293 int int_part = static_cast<int>(value + 0.5f); // Round
294 sstream stream;
295 stream << int_part;
296 return stream.str();
297 }
298
299 // For non-zero precision, use basic rounding
300 int multiplier = 1;
301 for (int i = 0; i < precision; ++i) {
302 multiplier *= 10;
303 }
304
305 // Handle rounding correctly for both positive and negative numbers
306 float scaled_float = value * multiplier;
307 int scaled;
308 if (scaled_float >= 0) {
309 scaled = static_cast<int>(scaled_float + 0.5f);
310 } else {
311 scaled = static_cast<int>(scaled_float - 0.5f);
312 }
313
314 int int_part = scaled / multiplier;
315 int frac_part = scaled % multiplier;
316
317 // For negative numbers, frac_part will be negative, so take absolute value
318 if (frac_part < 0) {
319 frac_part = -frac_part;
320 }
321
322 sstream stream;
323 // Preserve sign for values like -0.5 that round to int_part=0 (printed
324 // without sign by the integer write below).
325 if (value < 0 && int_part == 0) {
326 stream << "-";
327 }
328 stream << int_part;
329 stream << ".";
330
331 // Emit exactly `precision` fractional digits, zero-padded on the left.
332 // The previous implementation stopped padding once temp_multiplier dropped
333 // to 1, which produced "0.0"/"1.0" instead of "0.00"/"1.00" for any
334 // integer-valued input. It also omitted the digits entirely when
335 // frac_part == 0.
336 int temp_multiplier = multiplier / 10;
337 while (temp_multiplier > 0) {
338 int digit = (frac_part / temp_multiplier) % 10;
339 stream << static_cast<char>('0' + digit);
340 temp_multiplier /= 10;
341 }
342
343 return stream.str();
344}
345
346// Helper to convert pointer to fl::uptr (only instantiated for pointer types)
347template<typename T>
350 const void* vptr = static_cast<const void*>(ptr);
351 return reinterpret_cast<fl::uptr>(vptr); // ok reinterpret cast
352}
353
354// Overload for non-pointer types (never called at runtime, but needed for compilation)
355template<typename T>
358 return 0; // Never executed - runtime check prevents this
359}
360
361// Format non-pointer types (d, i, u, o, x, X, f, c, s)
362template<typename T>
364format_arg(sstream& stream, const FormatSpec& spec, const T& arg) FL_NOEXCEPT {
366 bool is_numeric = false;
367
368 switch (spec.type) {
369 case 'd':
370 case 'i': {
372 result = "<type_error>";
373 break;
374 }
375 is_numeric = true;
376
377 // Convert to string
378 sstream temp;
379 temp << arg;
380 result = temp.str();
381
382 // Handle sign flags
383 bool is_negative = !result.empty() && result[0] == '-';
384 if (!is_negative) {
385 if (spec.show_sign) {
386 result = fl::string("+") + result;
387 } else if (spec.space_sign) {
388 result = fl::string(" ") + result;
389 }
390 }
391 break;
392 }
393
394 case 'u': {
396 result = "<type_error>";
397 break;
398 }
399 is_numeric = true;
400
401 // Convert to string
402 sstream temp;
403 temp << arg;
404 result = temp.str();
405 break;
406 }
407
408 case 'o': {
410 result = "<type_error>";
411 break;
412 }
413 is_numeric = true;
414
415 // Convert to octal
416 result = to_octal(arg);
417
418 // Alternate form: prefix with 0 (but not for zero itself)
419 if (spec.alt_form && arg != 0) {
420 result = fl::string("0") + result;
421 }
422 break;
423 }
424
425 case 'x': {
426 is_numeric = true;
427
428 // Convert to hex
429 result = fl::to_hex(arg, spec.uppercase);
430
431 // Alternate form: prefix with 0x or 0X
432 if (spec.alt_form && arg != 0) {
433 result = fl::string(spec.uppercase ? "0X" : "0x") + result;
434 }
435 break;
436 }
437
438 case 'f': {
440 result = "<type_error>";
441 break;
442 }
443 is_numeric = true;
444
445 if (spec.precision >= 0) {
446 result = format_float(static_cast<float>(arg), spec.precision);
447 } else {
448 sstream temp;
449 temp << static_cast<float>(arg);
450 result = temp.str();
451 }
452 break;
453 }
454
455 case 'c': {
457 result = "<type_error>";
458 break;
459 }
460
461 char ch = static_cast<char>(arg);
462 char temp_str[2] = {ch, '\0'};
463 result = temp_str;
464 break;
465 }
466
467 case 's': {
468 sstream temp;
469 temp << arg;
470 result = temp.str();
471 break;
472 }
473
474 default:
475 result = "<unknown_format>";
476 break;
477 }
478
479 // Apply width and padding
480 result = apply_width(result, spec, is_numeric);
481
482 // Output final result
483 stream << result;
484}
485
486// Format pointer types (only handles 'p' format)
487template<typename T>
489format_arg(sstream& stream, const FormatSpec& spec, const T& arg) FL_NOEXCEPT {
491 bool is_numeric = false;
492
493 if (spec.type == 'p') {
494 // Pointer format - always prefix with "0x"
495 is_numeric = true;
496
497 // Convert pointer to integer address using SFINAE helper
498 fl::uptr addr = pointer_to_uptr(arg);
499
500 // Format as hex with 0x prefix (always use lowercase for standard compatibility)
501 result = fl::string("0x") + fl::to_hex(addr, false);
502 } else {
503 result = "<type_error>";
504 }
505
506 // Apply width and padding
507 result = apply_width(result, spec, is_numeric);
508
509 // Output final result
510 stream << result;
511}
512
513// Specialized format_arg for const char* (string literals)
514inline void format_arg(sstream& stream, const FormatSpec& spec, const char* arg) FL_NOEXCEPT {
516
517 bool is_numeric = false;
518
519 switch (spec.type) {
520 case 's':
521 if (arg) {
522 result = arg;
523 } else {
524 result = "(null)";
525 }
526 break;
527 case 'p': {
528 // Pointer format for const char*
529 is_numeric = true;
530 const void* ptr = static_cast<const void*>(arg);
531 fl::uptr addr = reinterpret_cast<fl::uptr>(ptr); // ok reinterpret cast
532 result = fl::string("0x") + fl::to_hex(addr, false);
533 break;
534 }
535 case 'x':
536 result = "<string_not_hex>";
537 break;
538 case 'd':
539 case 'i':
540 case 'u':
541 case 'o':
542 case 'f':
543 case 'c':
544 result = "<type_error>";
545 break;
546 default:
547 result = "<unknown_format>";
548 break;
549 }
550
551 // Apply width and padding
552 result = apply_width(result, spec, is_numeric);
553 stream << result;
554}
555
556// Specialized format_arg for char arrays (string literals like "hello")
557template<fl::size N>
558void format_arg(sstream& stream, const FormatSpec& spec, const char (&arg)[N]) FL_NOEXCEPT {
559 format_arg(stream, spec, static_cast<const char*>(arg));
560}
561
562// Base case: no more arguments
563inline void format_impl(sstream& stream, const char* format) FL_NOEXCEPT {
564 while (*format) {
565 if (*format == '%') {
567 if (spec.type == '%') {
568 stream << "%";
569 continue;
570 } else {
571 // No argument for format specifier
572 stream << "<missing_arg>";
573 continue;
574 }
575 } else {
576 // Create a single-character string since sstream treats char as number
577 char temp_str[2] = {*format, '\0'};
578 stream << temp_str;
579 ++format;
580 }
581 }
582}
583
584// Recursive case: process one argument and continue
585template<typename T, typename... Args>
586void format_impl(sstream& stream, const char* format, const T& first, const Args&... rest) FL_NOEXCEPT {
587 while (*format) {
588 if (*format == '%') {
590 if (spec.type == '%') {
591 stream << "%";
592 continue;
593 } else {
594 // Format the first argument and continue with the rest
595 format_arg(stream, spec, first);
596 format_impl(stream, format, rest...);
597 return;
598 }
599 } else {
600 // Create a single-character string since sstream treats char as number
601 char temp_str[2] = {*format, '\0'};
602 stream << temp_str;
603 ++format;
604 }
605 }
606
607 // If we get here, there are unused arguments
608 // This is not an error in printf, so we just ignore them
609}
610
611}
612
634template<typename... Args>
635void printf(const char* format, const Args&... args) FL_NOEXCEPT {
636 sstream stream;
638 fl::print(stream.str().c_str());
639}
640
665template<typename... Args>
666int snprintf(char* buffer, fl::size size, const char* format, const Args&... args) FL_NOEXCEPT {
667 // Handle null buffer or zero size
668 if (!buffer || size == 0) {
669 return 0;
670 }
671
672 // Format to internal string stream
673 sstream stream;
675 fl::string result = stream.str();
676
677 // Get the formatted string length
678 fl::size formatted_len = result.size();
679
680 // Copy to buffer, ensuring null termination
681 fl::size copy_len = (formatted_len < size - 1) ? formatted_len : size - 1;
682
683 // Copy characters
684 for (fl::size i = 0; i < copy_len; ++i) {
685 buffer[i] = result[i];
686 }
687
688 // Null terminate
689 buffer[copy_len] = '\0';
690
691 // Return the number of characters actually written (excluding null terminator)
692 // This respects the buffer size limit instead of returning the full formatted length
693 return static_cast<int>(copy_len);
694}
695
711template<fl::size N, typename... Args>
712int sprintf(char (&buffer)[N], const char* format, const Args&... args) FL_NOEXCEPT {
713 // Use the compile-time known buffer size for safety
714 return snprintf(buffer, N, format, args...);
715}
716
717} // namespace fl
uint8_t pos
Definition Blur.ino:11
fl::size length() const FL_NOEXCEPT
bool empty() const FL_NOEXCEPT
const char * c_str() const FL_NOEXCEPT
string str() const FL_NOEXCEPT
Definition strstream.h:43
fl::enable_if<!fl::is_pointer< T >::value >::type format_arg(sstream &stream, const FormatSpec &spec, const T &arg) FL_NOEXCEPT
Definition stdio.h:364
fl::string to_octal(T value) FL_NOEXCEPT
Definition stdio.h:201
fl::string format_float(float value, int precision) FL_NOEXCEPT
Definition stdio.h:282
fl::string apply_width(const fl::string &str, const FormatSpec &spec, bool is_numeric=false) FL_NOEXCEPT
Definition stdio.h:220
FormatSpec parse_format_spec(const char *&format) FL_NOEXCEPT
Definition stdio.h:109
fl::enable_if< fl::is_pointer< T >::value, fl::uptr >::type pointer_to_uptr(const T &ptr) FL_NOEXCEPT
Definition stdio.h:349
void format_impl(sstream &stream, const char *format) FL_NOEXCEPT
Definition stdio.h:563
constexpr int type_rank< T >::value
void print(const char *str)
void printf(const char *format, const Args &... args) FL_NOEXCEPT
Printf-like formatting function that prints directly to the platform output.
Definition stdio.h:635
int snprintf(char *buffer, fl::size size, const char *format, const Args &... args) FL_NOEXCEPT
Snprintf-like formatting function that writes to a buffer.
Definition stdio.h:666
fl::string to_hex(T value, bool uppercase=false, bool pad_to_width=false) FL_NOEXCEPT
Convert an integer value to hexadecimal string representation.
Definition string.h:517
int sprintf(char(&buffer)[N], const char *format, const Args &... args) FL_NOEXCEPT
Sprintf-like formatting function that writes to a buffer.
Definition stdio.h:712
expected< T, E > result
Alias for expected (Rust-style naming)
Definition result.h:31
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
FormatSpec(char t) FL_NOEXCEPT
Definition stdio.h:102
FormatSpec(char t, int prec) FL_NOEXCEPT
Definition stdio.h:103