Sound Byte Libs 29c5ff3
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_padded = [&](const char* s, int width) {
51 if (s == nullptr) {
52 s = "(null)";
53 }
54 // Measure length for padding
55 int slen = 0;
56 const char* p = s;
57 while (*p++) slen++;
58
59 // Right-justify: emit leading spaces
60 for (int i = slen; i < width; ++i) put_char(' ');
61 p = s;
62 while (*p) put_char(*p++);
63 };
64
65 // Render unsigned integer into tmp buffer, return length
66 auto render_uint = [](char* tmp, uint32_t val, int base, bool uppercase) -> int {
67 const char* hex_chars = uppercase ? "0123456789ABCDEF" : "0123456789abcdef";
68 int len = 0;
69 if (val == 0) {
70 tmp[len++] = '0';
71 } else {
72 char rev[12];
73 int rlen = 0;
74 while (val > 0) {
75 rev[rlen++] = hex_chars[val % base];
76 val /= base;
77 }
78 while (rlen > 0) tmp[len++] = rev[--rlen];
79 }
80 return len;
81 };
82
83 auto put_padded = [&](const char* digits, int len, int width, char fill, bool negative) {
84 int content_len = len + (negative ? 1 : 0);
85 if (fill == '0' && negative) {
86 put_char('-');
87 }
88 for (int i = content_len; i < width; ++i) put_char(fill);
89 if (fill != '0' && negative) {
90 put_char('-');
91 }
92 for (int i = 0; i < len; ++i) put_char(digits[i]);
93 };
94
95 auto put_uint_padded = [&](uint32_t val, int base, bool uppercase, int width, char fill) {
96 char tmp[12];
97 int len = render_uint(tmp, val, base, uppercase);
98 put_padded(tmp, len, width, fill, false);
99 };
100
101 auto put_int_padded = [&](int32_t val, int width, char fill) {
102 bool neg = val < 0;
103 uint32_t uval = neg ? static_cast<uint32_t>(-val) : static_cast<uint32_t>(val);
104 char tmp[12];
105 int len = render_uint(tmp, uval, 10, false);
106 put_padded(tmp, len, width, fill, neg);
107 };
108
109 auto put_float = [&](double val, int precision, int width, char fill) {
110 // Render to temporary buffer, then pad
111 char tmp[24];
112 int tpos = 0;
113 bool neg = false;
114
115 if (val < 0) {
116 neg = true;
117 val = -val;
118 }
119
120 // Integer part
121 uint32_t int_part = static_cast<uint32_t>(val);
122 tpos += render_uint(tmp + tpos, int_part, 10, false);
123
124 // Decimal point + fractional
125 tmp[tpos++] = '.';
126 double frac = val - int_part;
127 for (int i = 0; i < precision; i++) {
128 frac *= 10;
129 int digit = static_cast<int>(frac);
130 tmp[tpos++] = static_cast<char>('0' + digit);
131 frac -= digit;
132 }
133
134 put_padded(tmp, tpos, width, fill, neg);
135 };
136
137 while (*fmt) {
138 if (*fmt != '%') {
139 put_char(*fmt++);
140 continue;
141 }
142
143 fmt++; // Skip '%'
144
145 // Parse flags
146 char fill = ' ';
147 if (*fmt == '0') {
148 fill = '0';
149 fmt++;
150 }
151
152 // Parse width
153 int width = 0;
154 while (*fmt >= '0' && *fmt <= '9') {
155 width = width * 10 + (*fmt - '0');
156 fmt++;
157 }
158
159 // Handle precision for floats
160 int precision = 2; // Default precision
161 if (*fmt == '.') {
162 fmt++;
163 precision = 0;
164 while (*fmt >= '0' && *fmt <= '9') {
165 precision = precision * 10 + (*fmt - '0');
166 fmt++;
167 }
168 }
169
170 // Handle length modifiers (l, ll, h, hh)
171 bool is_long = false;
172 if (*fmt == 'l') {
173 is_long = true;
174 fmt++;
175 if (*fmt == 'l') {
176 fmt++; // Skip second 'l' (treat ll same as l for 32-bit)
177 }
178 } else if (*fmt == 'h') {
179 fmt++;
180 if (*fmt == 'h') {
181 fmt++;
182 }
183 }
184
185 switch (*fmt) {
186 case 'd':
187 case 'i':
188 if (is_long) {
189 put_int_padded(static_cast<int32_t>(va_arg(args, long)), width, fill);
190 } else {
191 put_int_padded(va_arg(args, int), width, fill);
192 }
193 break;
194
195 case 'u':
196 if (is_long) {
197 put_uint_padded(static_cast<uint32_t>(va_arg(args, unsigned long)), 10, false, width, fill);
198 } else {
199 put_uint_padded(va_arg(args, unsigned int), 10, false, width, fill);
200 }
201 break;
202
203 case 'x':
204 if (is_long) {
205 put_uint_padded(static_cast<uint32_t>(va_arg(args, unsigned long)), 16, false, width, fill);
206 } else {
207 put_uint_padded(va_arg(args, unsigned int), 16, false, width, fill);
208 }
209 break;
210
211 case 'X':
212 if (is_long) {
213 put_uint_padded(static_cast<uint32_t>(va_arg(args, unsigned long)), 16, true, width, fill);
214 } else {
215 put_uint_padded(va_arg(args, unsigned int), 16, true, width, fill);
216 }
217 break;
218
219 case 's':
220 put_string_padded(va_arg(args, const char*), width);
221 break;
222
223 case 'c':
224 put_char(static_cast<char>(va_arg(args, int)));
225 break;
226
227 case 'f':
228 put_float(va_arg(args, double), precision, width, fill);
229 break;
230
231 case '%':
232 put_char('%');
233 break;
234
235 case '\0':
236 // End of string after %
237 goto done;
238
239 default:
240 // Unknown specifier, output as-is
241 put_char('%');
242 put_char(*fmt);
243 break;
244 }
245
246 fmt++;
247 }
248
249done:
250 // Null terminate
251 buf[pos < size ? pos : max_pos] = '\0';
252
253 return static_cast<int>(pos);
254}
255
256/**
257 * @brief Format a string into a buffer (snprintf-style)
258 *
259 * @param buf Output buffer
260 * @param size Buffer size
261 * @param fmt Format string
262 * @param ... Arguments
263 * @return Number of characters that would have been written (excluding null)
264 */
265inline int format(char* buf, size_t size, const char* fmt, ...) {
266 va_list args;
267 va_start(args, fmt);
268 int result = vformat(buf, size, fmt, args);
269 va_end(args);
270 return result;
271}
272
273} // namespace sbl::log
274
275#endif // SBL_LOG_FORMAT_HPP_
Lightweight logging system.
Definition banner.hpp:50
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:265