Sound Byte Libs 1ee2ca6
C++ firmware library for audio applications on 32-bit ARM Cortex-M processors
Loading...
Searching...
No Matches
format.hpp
Go to the documentation of this file.
1/**
2 * @file format.hpp
3 * @brief Minimal safe string formatting for embedded systems
4 *
5 * Provides basic printf-style formatting optimized for embedded use:
6 * - %d, %i: signed integer
7 * - %u: unsigned integer
8 * - %x, %X: hex (lower/upper)
9 * - %s: string
10 * - %c: character
11 * - %f: float (fixed precision, no scientific notation)
12 * - %%: literal percent
13 *
14 * No dynamic allocation. Fixed buffer output.
15 */
16
17#ifndef SBL_LOG_FORMAT_HPP_
18#define SBL_LOG_FORMAT_HPP_
19
20#include <cstdint>
21#include <cstddef>
22#include <cstdarg>
23
24namespace sbl::log {
25
26/**
27 * @brief Format a string into a buffer (vsnprintf-style)
28 *
29 * @param buf Output buffer
30 * @param size Buffer size
31 * @param fmt Format string
32 * @param args Variadic argument list
33 * @return Number of characters that would have been written (excluding null)
34 */
35inline int vformat(char* buf, size_t size, const char* fmt, va_list args) {
36 if (size == 0 || buf == nullptr || fmt == nullptr) {
37 return 0;
38 }
39
40 size_t pos = 0;
41 const size_t max_pos = size - 1; // Reserve space for null terminator
42
43 auto put_char = [&](char c) {
44 if (pos < max_pos) {
45 buf[pos] = c;
46 }
47 pos++;
48 };
49
50 auto put_string = [&](const char* s) {
51 if (s == nullptr) {
52 s = "(null)";
53 }
54 while (*s) {
55 put_char(*s++);
56 }
57 };
58
59 auto put_uint = [&](uint32_t val, int base, bool uppercase) {
60 char digits[12]; // Enough for 32-bit
61 int len = 0;
62 const char* hex_chars = uppercase ? "0123456789ABCDEF" : "0123456789abcdef";
63
64 if (val == 0) {
65 put_char('0');
66 return;
67 }
68
69 while (val > 0) {
70 digits[len++] = hex_chars[val % base];
71 val /= base;
72 }
73
74 while (len > 0) {
75 put_char(digits[--len]);
76 }
77 };
78
79 auto put_int = [&](int32_t val) {
80 if (val < 0) {
81 put_char('-');
82 val = -val;
83 }
84 put_uint(static_cast<uint32_t>(val), 10, false);
85 };
86
87 auto put_float = [&](double val, int precision) {
88 // Handle negative
89 if (val < 0) {
90 put_char('-');
91 val = -val;
92 }
93
94 // Integer part
95 uint32_t int_part = static_cast<uint32_t>(val);
96 put_uint(int_part, 10, false);
97
98 // Decimal point
99 put_char('.');
100
101 // Fractional part
102 double frac = val - int_part;
103 for (int i = 0; i < precision; i++) {
104 frac *= 10;
105 int digit = static_cast<int>(frac);
106 put_char('0' + digit);
107 frac -= digit;
108 }
109 };
110
111 while (*fmt) {
112 if (*fmt != '%') {
113 put_char(*fmt++);
114 continue;
115 }
116
117 fmt++; // Skip '%'
118
119 // Handle width (simple: just skip digits for now)
120 while (*fmt >= '0' && *fmt <= '9') {
121 fmt++;
122 }
123
124 // Handle precision for floats
125 int precision = 2; // Default precision
126 if (*fmt == '.') {
127 fmt++;
128 precision = 0;
129 while (*fmt >= '0' && *fmt <= '9') {
130 precision = precision * 10 + (*fmt - '0');
131 fmt++;
132 }
133 }
134
135 // Handle length modifiers (l, ll, h, hh)
136 bool is_long = false;
137 if (*fmt == 'l') {
138 is_long = true;
139 fmt++;
140 if (*fmt == 'l') {
141 fmt++; // Skip second 'l' (treat ll same as l for 32-bit)
142 }
143 } else if (*fmt == 'h') {
144 fmt++;
145 if (*fmt == 'h') {
146 fmt++;
147 }
148 }
149
150 switch (*fmt) {
151 case 'd':
152 case 'i':
153 if (is_long) {
154 put_int(static_cast<int32_t>(va_arg(args, long)));
155 } else {
156 put_int(va_arg(args, int));
157 }
158 break;
159
160 case 'u':
161 if (is_long) {
162 put_uint(static_cast<uint32_t>(va_arg(args, unsigned long)), 10, false);
163 } else {
164 put_uint(va_arg(args, unsigned int), 10, false);
165 }
166 break;
167
168 case 'x':
169 if (is_long) {
170 put_uint(static_cast<uint32_t>(va_arg(args, unsigned long)), 16, false);
171 } else {
172 put_uint(va_arg(args, unsigned int), 16, false);
173 }
174 break;
175
176 case 'X':
177 if (is_long) {
178 put_uint(static_cast<uint32_t>(va_arg(args, unsigned long)), 16, true);
179 } else {
180 put_uint(va_arg(args, unsigned int), 16, true);
181 }
182 break;
183
184 case 's':
185 put_string(va_arg(args, const char*));
186 break;
187
188 case 'c':
189 put_char(static_cast<char>(va_arg(args, int)));
190 break;
191
192 case 'f':
193 put_float(va_arg(args, double), precision);
194 break;
195
196 case '%':
197 put_char('%');
198 break;
199
200 case '\0':
201 // End of string after %
202 goto done;
203
204 default:
205 // Unknown specifier, output as-is
206 put_char('%');
207 put_char(*fmt);
208 break;
209 }
210
211 fmt++;
212 }
213
214done:
215 // Null terminate
216 buf[pos < size ? pos : max_pos] = '\0';
217
218 return static_cast<int>(pos);
219}
220
221/**
222 * @brief Format a string into a buffer (snprintf-style)
223 *
224 * @param buf Output buffer
225 * @param size Buffer size
226 * @param fmt Format string
227 * @param ... Arguments
228 * @return Number of characters that would have been written (excluding null)
229 */
230inline int format(char* buf, size_t size, const char* fmt, ...) {
231 va_list args;
232 va_start(args, fmt);
233 int result = vformat(buf, size, fmt, args);
234 va_end(args);
235 return result;
236}
237
238} // namespace sbl::log
239
240#endif // SBL_LOG_FORMAT_HPP_
int vformat(char *buf, size_t size, const char *fmt, va_list args)
Format a string into a buffer (vsnprintf-style)
Definition format.hpp:35
int format(char *buf, size_t size, const char *fmt,...)
Format a string into a buffer (snprintf-style)
Definition format.hpp:230