Anklang 0.3.0-460-gc4ef46ba
ASE — Anklang Sound Engine (C++)

« « « Anklang Documentation
Loading...
Searching...
No Matches
formatter.cc
Go to the documentation of this file.
1 // This Source Code Form is licensed MPL-2.0: http://mozilla.org/MPL/2.0
2#include "formatter.hh"
3#include <unistd.h> // isatty
4#include <cstring>
5#include <atomic>
6#include "testing.hh"
7#include "internal.hh"
8
14namespace Ase {
15
16locale_t
17ScopedPosixLocale::posix_locale()
18{
19 static locale_t cached_posix_locale = [] () {
20 locale_t posix_locale = nullptr;
21 if (!posix_locale)
22 posix_locale = newlocale (LC_ALL_MASK, "POSIX.UTF-8", nullptr);
23 if (!posix_locale)
24 posix_locale = newlocale (LC_ALL_MASK, "C.UTF-8", nullptr);
25 if (!posix_locale)
26 posix_locale = newlocale (LC_ALL_MASK, "POSIX", nullptr);
27 if (!posix_locale)
28 posix_locale = newlocale (LC_ALL_MASK, "C", nullptr);
29 if (!posix_locale)
30 posix_locale = newlocale (LC_ALL_MASK, nullptr, nullptr);
31 if (posix_locale == nullptr)
32 fprintf (stderr, "%s: WARNING: newlocale() returned NULL\n", __FILE__);
33 return posix_locale;
34 } ();
35 return cached_posix_locale;
36}
37
38ScopedPosixLocale::ScopedPosixLocale()
39{
40 saved_locale_ = uselocale (posix_locale());
41}
42
43ScopedPosixLocale::~ScopedPosixLocale()
44{
45 uselocale (saved_locale_);
46}
47
48namespace Impl {
49
50template<class... Args> static std::string
51system_string_printf (const char *format, Args... args)
52{
53 char *cstr = nullptr;
54 int ret;
55 {
56 ScopedPosixLocale posix_locale;
57 ret = asprintf (&cstr, format, args...);
58 }
59 if (ret >= 0 && cstr)
60 {
61 std::string result = cstr;
62 free (cstr);
63 return result;
64 }
65 return format;
66}
67
68static bool
69parse_unsigned_integer (const char **stringp, uint64_t *up)
70{ // '0' | [1-9] [0-9]* : <= 18446744073709551615
71 const char *p = *stringp;
72 // zero
73 if (*p == '0' && !(p[1] >= '0' && p[1] <= '9'))
74 {
75 *up = 0;
76 *stringp = p + 1;
77 return true;
78 }
79 // first digit
80 if (!(*p >= '1' && *p <= '9'))
81 return false;
82 uint64_t u = *p - '0';
83 p++;
84 // rest digits
85 while (*p >= '0' && *p <= '9')
86 {
87 const uint64_t last = u;
88 u = u * 10 + (*p - '0');
89 p++;
90 if (u < last) // overflow
91 return false;
92 }
93 *up = u;
94 *stringp = p;
95 return true;
96}
97
99 using ll_t = long long;
100 char conversion = 0;
101 uint32_t adjust_left : 1 = 0, add_sign : 1 = 0, use_width : 1 = 0, use_precision : 1 = 0;
102 uint32_t alternate_form : 1 = 0, zero_padding : 1 = 0, add_space : 1 = 0, locale_grouping : 1 = 0;
103 uint32_t field_width = 0, precision = 0, start = 0, end = 0, value_index = 0, width_index = 0, precision_index = 0;
104 static bool
105 parse_positional (const char **stringp, uint64_t *ap)
106 { // [0-9]+ '$'
107 const char *p = *stringp;
108 uint64_t ui64 = 0;
109 if (parse_unsigned_integer (&p, &ui64) && *p == '$')
110 {
111 p++;
112 *ap = ui64;
113 *stringp = p;
114 return true;
115 }
116 return false;
117 }
118 const char*
119 parse_directive (const char **stringp, size_t *indexp)
120 { // '%' positional? [-+#0 '']* ([0-9]*|[*]positional?) ([.]([0-9]*|[*]positional?))? [hlLjztqZ]* [spmcCdiouXxFfGgEeAa]
121 const char *p = *stringp;
122 size_t index = *indexp;
123 // '%' directive start
124 if (*p != '%')
125 return "missing '%' at start";
126 p++;
127 // positional argument
128 uint64_t ui64 = -1;
129 if (parse_positional (&p, &ui64))
130 {
131 if (ui64 > 0 && ui64 <= 2147483647)
132 value_index = ui64;
133 else
134 return "invalid positional specification";
135 }
136 // flags
137 const char *flags = "-+#0 '";
138 while (strchr (flags, *p))
139 switch (*p)
140 {
141 case '-': adjust_left = true; goto default_case;
142 case '+': add_sign = true; goto default_case;
143 case '#': alternate_form = true; goto default_case;
144 case '0': zero_padding = true; goto default_case;
145 case ' ': add_space = true; goto default_case;
146 case '\'': locale_grouping = true; goto default_case;
147 default: default_case:
148 p++;
149 break;
150 }
151 // field width
152 ui64 = 0;
153 if (*p == '*')
154 {
155 p++;
156 if (parse_positional (&p, &ui64))
157 {
158 if (ui64 > 0 && ui64 <= 2147483647)
159 width_index = ui64;
160 else
161 return "invalid positional specification";
162 }
163 else
164 width_index = index++;
165 use_width = true;
166 }
167 else if (parse_unsigned_integer (&p, &ui64))
168 {
169 if (ui64 <= 2147483647)
170 field_width = ui64;
171 else
172 return "invalid field width specification";
173 use_width = true;
174 }
175 // precision
176 if (*p == '.')
177 {
178 use_precision = true;
179 p++;
180 }
181 if (*p == '*')
182 {
183 p++;
184 if (parse_positional (&p, &ui64))
185 {
186 if (ui64 > 0 && ui64 <= 2147483647)
187 precision_index = ui64;
188 else
189 return "invalid positional specification";
190 }
191 else
192 precision_index = index++;
193 }
194 else if (parse_unsigned_integer (&p, &ui64))
195 {
196 if (ui64 <= 2147483647)
197 precision = ui64;
198 else
199 return "invalid precision specification";
200 }
201 // modifiers
202 const char *modifiers = "hlLjztqZ";
203 while (strchr (modifiers, *p))
204 p++;
205 // conversion
206 const char *conversion_chars = "dioucCspmXxEeFfGgAa%";
207 if (!strchr (conversion_chars, *p))
208 return "missing conversion specifier";
209 if (value_index == 0 && !strchr ("m%", *p))
210 value_index = index++;
211 conversion = *p++;
212 if (conversion == 'C') // %lc in SUSv2
213 conversion = 'c';
214 // success
215 *indexp = index;
216 *stringp = p;
217 return nullptr; // OK
218 }
220 render_directive (const size_t N, const StringFormatArg *args) const
221 {
222 switch (conversion)
223 {
224 case 'm':
225 return render_value (N, args, "", int (0)); // dummy arg to silence compiler
226 case 'p':
227 return render_value (N, args, "", arg_as_ptr (N, args, value_index));
228 case 's': // precision
229 return render_value (N, args, "", arg_as_chars (N, args, value_index));
230 case 'c': case 'd': case 'i': case 'o': case 'u': case 'X': case 'x':
231 return render_value (N, args, "ll", arg_as_longlong (N, args, value_index));
232 case 'f': case 'F': case 'e': case 'E': case 'g': case 'G': case 'a': case 'A':
233 return render_value (N, args, "", arg_as_double (N, args, value_index));
234 case '%':
235 return "%";
236 }
237 return std::string ("%") + conversion;
238 }
239 static const StringFormatArg&
240 format_arg (const size_t N, const StringFormatArg *args, size_t nth)
241 {
242 if (nth && nth <= N)
243 return args[nth-1];
244 static const StringFormatArg zero_arg;
245 return zero_arg;
246 }
247 static const char*
248 arg_as_chars (const size_t N, const StringFormatArg *args, size_t nth)
249 {
250 if (!(nth && nth <= N))
251 return "";
252 const StringFormatArg &arg = format_arg (N, args, nth);
253 if (auto *p = std::get_if<const char*> (&arg))
254 return *p ? *p : "(null)";
255 if (auto *p = std::get_if<uint64_t> (&arg); *p == 0)
256 return "(null)";
257 return "";
258 }
259 static void*
260 arg_as_ptr (const size_t N, const StringFormatArg *args, size_t nth)
261 {
262 return (void*) ptrdiff_t (arg_as_longlong (N, args, nth));
263 }
264 static uint32_t
265 arg_as_width (const size_t N, const StringFormatArg *args, size_t nth)
266 {
267 int32_t w = arg_as_longlong (N, args, nth);
268 w = std::abs (w);
269 return w < 0 ? std::abs (w + 1) : w; // turn -2147483648 into +2147483647
270 }
271 static uint32_t
272 arg_as_precision (const size_t N, const StringFormatArg *args, size_t nth)
273 {
274 const int32_t precision = arg_as_longlong (N, args, nth);
275 return std::max (0, precision);
276 }
277 static ll_t
278 arg_as_longlong (const size_t N, const StringFormatArg *args, size_t nth)
279 {
280 const StringFormatArg &arg = format_arg (N, args, nth);
281 switch (arg.index())
282 {
283 case 0: return std::get<uint64_t> (arg);
284 case 1: return ll_t (std::get<double> (arg));
285 case 2: return ll_t (std::get<const char*> (arg));
286 }
287 return 0;
288 }
289 static double
290 arg_as_double (const size_t N, const StringFormatArg *args, size_t nth)
291 {
292 const StringFormatArg &arg = format_arg (N, args, nth);
293 switch (arg.index())
294 {
295 case 0: return std::get<uint64_t> (arg);
296 case 1: return std::get<double> (arg);
297 case 2: return double (ptrdiff_t (std::get<const char*> (arg)));
298 }
299 return 0.0;
300 }
301 template<class Value> std::string
302 render_value (const size_t N, const StringFormatArg *args, const char *modifier, Value value) const
303 {
304 std::string format;
305 const int field_width = !use_width || !width_index ? this->field_width : arg_as_width (N, args, width_index);
306 const int field_precision = !use_precision || !precision_index ? std::max (uint32_t (0), precision) : arg_as_precision (N, args, precision_index);
307 // format directive
308 format += '%';
309 if (adjust_left)
310 format += '-';
311 if (add_sign)
312 format += '+';
313 if (add_space)
314 format += ' ';
315 if (zero_padding && !adjust_left && strchr ("diouXx" "FfGgEeAa", conversion))
316 format += '0';
317 if (alternate_form && strchr ("oXx" "FfGgEeAa", conversion))
318 format += '#';
319 if (locale_grouping && strchr ("idu" "FfGg", conversion))
320 format += '\'';
321 if (use_width)
322 format += '*';
323 if (use_precision && strchr ("sm" "diouXx" "FfGgEeAa", conversion)) // !cp
324 format += ".*";
325 if (modifier)
326 format += modifier;
327 format += conversion;
328 // printf formatting
329 if (use_width && use_precision)
330 return system_string_printf (format.c_str(), field_width, field_precision, value);
331 else if (use_precision)
332 return system_string_printf (format.c_str(), field_precision, value);
333 else if (use_width)
334 return system_string_printf (format.c_str(), field_width, value);
335 else
336 return system_string_printf (format.c_str(), value);
337 }
338};
339
340static size_t
341upper_directive_count (const char *format)
342{
343 size_t n = 0;
344 for (const char *p = format; *p; p++)
345 if (p[0] == '%') // count %...
346 {
347 n++;
348 if (p[1] == '%') // dont count %% twice
349 p++;
350 }
351 return n;
352}
353
354static std::string
355format_error (const char *err, const char *format, size_t directive)
356{
357 const char *cyan = "", *cred = "", *cyel = "", *crst = "";
358 if (isatty (fileno (stderr)))
359 {
360 const char *term = getenv ("TERM");
361 if (term && strcmp (term, "dumb") != 0)
362 {
363 cyan = "\033[36m";
364 cred = "\033[31m\033[1m";
365 cyel = "\033[33m";
366 crst = "\033[39m\033[22m";
367 }
368 }
369 if (directive)
370 fprintf (stderr, "%sStringFormatter: %sWARNING:%s%s %s in directive %zu:%s %s\n", cyan, cred, crst, cyel, err, directive, crst, format);
371 else
372 fprintf (stderr, "%sStringFormatter: %sWARNING:%s%s %s:%s %s\n", cyan, cred, crst, cyel, err, crst, format);
373 return format;
374}
375
377StringFormatArg::string_format_args (const char *format, const size_t N, const StringFormatArg *args)
378{
379 if (!format)
380 return format_error ("format is nullptr", "<nullptr>", 0);
381 // allocate enough space to hold all directives possibly contained in format
382 const size_t max_dirs = 1 + upper_directive_count (format);
383 StringFormatDirective fdirs[max_dirs];
384 // parse format into Directive stack
385 size_t nextarg = 1, ndirs = 0;
386 const char *p = format;
387 while (*p)
388 {
389 do
390 {
391 if (p[0] == '%')
392 break;
393 p++;
394 }
395 while (*p);
396 if (*p == 0)
397 break;
398 const size_t start = p - format;
399 const char *err = fdirs[ndirs].parse_directive (&p, &nextarg);
400 if (err)
401 return format_error (err, format, ndirs + 1);
402 fdirs[ndirs].start = start;
403 fdirs[ndirs].end = p - format;
404 ndirs++;
405 TASSERT (ndirs < max_dirs);
406 }
407 const size_t argcounter = nextarg - 1;
408 fdirs[ndirs].end = fdirs[ndirs].start = p - format;
409 // check maximum argument reference and argument count
410 size_t argmaxref = argcounter;
411 for (size_t i = 0; i < ndirs; i++)
412 {
413 const StringFormatDirective &fdir = fdirs[i];
414 argmaxref = std::max (argmaxref, size_t (fdir.value_index));
415 argmaxref = std::max (argmaxref, size_t (fdir.width_index));
416 argmaxref = std::max (argmaxref, size_t (fdir.precision_index));
417 }
418 if (argmaxref > N)
419 return format_error ("too few arguments for format", format, 0);
420 if (argmaxref < N)
421 return format_error ("too many arguments for format", format, 0);
422 // format pieces
423 std::string result;
424 p = format;
425 for (size_t i = 0; i <= ndirs; i++)
426 {
427 const StringFormatDirective &fdir = fdirs[i];
428 result += std::string (p, fdir.start - (p - format));
429 if (fdir.conversion)
430 result += fdir.render_directive (N, args);
431 p = format + fdir.end;
432 }
433 return result;
434}
435
436} // Impl
437
438} // Ase
439
440// == Testing ==
441namespace { // Anon
442struct UncopyablePoint {
443 double x, y;
444 friend inline std::ostream&
445 operator<< (std::ostream &s, const UncopyablePoint &p) { return s << "{" << p.x << ";" << p.y << "}"; }
446 UncopyablePoint (double _x, double _y) : x (_x), y (_y) {}
447 ASE_CLASS_NON_COPYABLE (UncopyablePoint);
448};
449
450TEST_INTEGRITY (ase_string_format);
451static void
452ase_string_format()
453{
454 using namespace Ase;
455 std::atomic<bool> boolean = 1;
456 // string_format
457 TCMP (string_format ("%d", bool (1)), ==, "1");
458 TCMP (string_format ("%d", char (-17)), ==, "-17");
459 TCMP (string_format ("%s", nullptr), ==, "(null)");
460 TCMP (string_format ("%s", boolean), ==, "1");
461 TCMP (string_format ("%s", (char*) "FOO"), ==, "FOO");
462 TCMP (string_format ("%s", (void*) "FOO"), ==, "");
463 TCMP (string_format ("%d %s", -9223372036854775808uLL, "FOO"), ==, "-9223372036854775808 FOO");
464 TCMP (string_format ("0x%08x", 0xc0ffee), ==, "0x00c0ffee");
465 enum { TEST17 = 17 };
466 TCMP (string_format ("%g %d", 0.5, TEST17), ==, "0.5 17");
467 static_assert (TEST17 == 17, "!");
468 TCMP (string_format ("Only %c%%", '3'), ==, "Only 3%");
469 // ostream tests
470 UncopyablePoint point { 1, 2 };
471 TCMP (string_format ("%s", point), ==, "{1;2}");
472 TCMP (string_format ("%s+%s+%s", point, point, point), ==, "{1;2}+{1;2}+{1;2}");
473 String sfoo ("foo");
474 typedef char MutableChar;
475 MutableChar *foo = &sfoo[0];
476 TCMP (string_format ("%s", foo), ==, "foo");
477 // test robustness for arcane/rarely-used width modifiers
478 const char *arcane_format = "| %qd %Zd %LF |";
479 TCMP (string_format (arcane_format, (long long) 1234, size_t (4321), (long double) 1234.), ==, "| 1234 4321 1234.000000 |");
480 TCMP (string_format ("- %C - %lc -", long ('X'), long ('x')), ==, "- X - x -");
481 // TCMP (string_format ("+ %S +", (wchar_t*) "\1\1\1\1\0\0\0\0"), ==, "+ \1\1\1\1 +");
482}
483
484} // Anon
#define ASE_CLASS_NON_COPYABLE(ClassName)
Delete copy ctor and assignment operator.
Definition cxxaux.hh:106
fprintf
free
getenv
T index(T... args)
#define TEST_INTEGRITY(FUNC)
Register func as an integrity test.
Definition internal.hh:77
typedef double
T max(T... args)
The Anklang C++ API namespace.
Definition api.hh:9
newlocale
typedef uint64_t
strchr
Value type used to interface with various property types.
Definition value.hh:54
typedef long
#define TASSERT(cond)
Unconditional test assertion, enters breakpoint if not fullfilled.
Definition testing.hh:24
#define TCMP(a, cmp, b)
Compare a and b according to operator cmp, verbose on failiure.
Definition testing.hh:23
uselocale