Anklang-0.3.0.dev595+g65331842 anklang-0.3.0.dev595+g65331842
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':
231 return render_value (N, args, "", arg_as_char (N, args, value_index));
232 case 'd': case 'i': case 'o': case 'u': case 'X': case 'x':
233 return render_value (N, args, "ll", arg_as_longlong (N, args, value_index));
234 case 'f': case 'F': case 'e': case 'E': case 'g': case 'G': case 'a': case 'A':
235 return render_value (N, args, "", arg_as_double (N, args, value_index));
236 case '%':
237 return "%";
238 }
239 return std::string ("%") + conversion;
240 }
241 static const StringFormatArg&
242 format_arg (const size_t N, const StringFormatArg *args, size_t nth)
243 {
244 if (nth && nth <= N)
245 return args[nth-1];
246 static const StringFormatArg zero_arg;
247 return zero_arg;
248 }
249 static const char*
250 arg_as_chars (const size_t N, const StringFormatArg *args, size_t nth)
251 {
252 if (!(nth && nth <= N))
253 return "";
254 const StringFormatArg &arg = format_arg (N, args, nth);
255 if (auto *p = std::get_if<const char*> (&arg))
256 return *p ? *p : "(null)";
257 if (auto *p = std::get_if<uint64_t> (&arg); *p == 0)
258 return "(null)";
259 return "";
260 }
261 static void*
262 arg_as_ptr (const size_t N, const StringFormatArg *args, size_t nth)
263 {
264 return (void*) ptrdiff_t (arg_as_longlong (N, args, nth));
265 }
266 static uint32_t
267 arg_as_width (const size_t N, const StringFormatArg *args, size_t nth)
268 {
269 int32_t w = arg_as_longlong (N, args, nth);
270 w = std::abs (w);
271 return w < 0 ? std::abs (w + 1) : w; // turn -2147483648 into +2147483647
272 }
273 static uint32_t
274 arg_as_precision (const size_t N, const StringFormatArg *args, size_t nth)
275 {
276 const int32_t precision = arg_as_longlong (N, args, nth);
277 return std::max (0, precision);
278 }
279 static char
280 arg_as_char (const size_t N, const StringFormatArg *args, size_t nth)
281 {
282 return arg_as_longlong (N, args, nth);
283 }
284 static ll_t
285 arg_as_longlong (const size_t N, const StringFormatArg *args, size_t nth)
286 {
287 const StringFormatArg &arg = format_arg (N, args, nth);
288 switch (arg.index())
289 {
290 case 0: return std::get<uint64_t> (arg);
291 case 1: return ll_t (std::get<double> (arg));
292 case 2: return ll_t (std::get<const char*> (arg));
293 }
294 return 0;
295 }
296 static double
297 arg_as_double (const size_t N, const StringFormatArg *args, size_t nth)
298 {
299 const StringFormatArg &arg = format_arg (N, args, nth);
300 switch (arg.index())
301 {
302 case 0: return std::get<uint64_t> (arg);
303 case 1: return std::get<double> (arg);
304 case 2: return double (ptrdiff_t (std::get<const char*> (arg)));
305 }
306 return 0.0;
307 }
308 template<class Value> std::string
309 render_value (const size_t N, const StringFormatArg *args, const char *modifier, Value value) const
310 {
311 std::string format;
312 const int field_width = !use_width || !width_index ? this->field_width : arg_as_width (N, args, width_index);
313 const int field_precision = !use_precision || !precision_index ? std::max (uint32_t (0), precision) : arg_as_precision (N, args, precision_index);
314 // format directive
315 format += '%';
316 if (adjust_left)
317 format += '-';
318 if (add_sign)
319 format += '+';
320 if (add_space)
321 format += ' ';
322 if (zero_padding && !adjust_left && strchr ("diouXx" "FfGgEeAa", conversion))
323 format += '0';
324 if (alternate_form && strchr ("oXx" "FfGgEeAa", conversion))
325 format += '#';
326 if (locale_grouping && strchr ("idu" "FfGg", conversion))
327 format += '\'';
328 if (use_width)
329 format += '*';
330 if (use_precision && strchr ("sm" "diouXx" "FfGgEeAa", conversion)) // !cp
331 format += ".*";
332 if (modifier)
333 format += modifier;
334 format += conversion;
335 // printf formatting
336 if (use_width && use_precision)
337 return system_string_printf (format.c_str(), field_width, field_precision, value);
338 else if (use_precision)
339 return system_string_printf (format.c_str(), field_precision, value);
340 else if (use_width)
341 return system_string_printf (format.c_str(), field_width, value);
342 else
343 return system_string_printf (format.c_str(), value);
344 }
345};
346
347static size_t
348upper_directive_count (const char *format)
349{
350 size_t n = 0;
351 for (const char *p = format; *p; p++)
352 if (p[0] == '%') // count %...
353 {
354 n++;
355 if (p[1] == '%') // dont count %% twice
356 p++;
357 }
358 return n;
359}
360
361static std::string
362format_error (const char *err, const char *format, size_t directive)
363{
364 const char *cyan = "", *cred = "", *cyel = "", *crst = "";
365 if (isatty (fileno (stderr)))
366 {
367 const char *term = getenv ("TERM");
368 if (term && strcmp (term, "dumb") != 0)
369 {
370 cyan = "\033[36m";
371 cred = "\033[31m\033[1m";
372 cyel = "\033[33m";
373 crst = "\033[39m\033[22m";
374 }
375 }
376 if (directive)
377 fprintf (stderr, "%sStringFormatter: %sWARNING:%s%s %s in directive %zu:%s %s\n", cyan, cred, crst, cyel, err, directive, crst, format);
378 else
379 fprintf (stderr, "%sStringFormatter: %sWARNING:%s%s %s:%s %s\n", cyan, cred, crst, cyel, err, crst, format);
380 return format;
381}
382
384StringFormatArg::string_format_args (const char *format, const size_t N, const StringFormatArg *args)
385{
386 if (!format)
387 return format_error ("format is nullptr", "<nullptr>", 0);
388 // allocate enough space to hold all directives possibly contained in format
389 const size_t max_dirs = 1 + upper_directive_count (format);
390 StringFormatDirective fdirs[max_dirs];
391 // parse format into Directive stack
392 size_t nextarg = 1, ndirs = 0;
393 const char *p = format;
394 while (*p)
395 {
396 do
397 {
398 if (p[0] == '%')
399 break;
400 p++;
401 }
402 while (*p);
403 if (*p == 0)
404 break;
405 const size_t start = p - format;
406 const char *err = fdirs[ndirs].parse_directive (&p, &nextarg);
407 if (err)
408 return format_error (err, format, ndirs + 1);
409 fdirs[ndirs].start = start;
410 fdirs[ndirs].end = p - format;
411 ndirs++;
412 TASSERT (ndirs < max_dirs);
413 }
414 const size_t argcounter = nextarg - 1;
415 fdirs[ndirs].end = fdirs[ndirs].start = p - format;
416 // check maximum argument reference and argument count
417 size_t argmaxref = argcounter;
418 for (size_t i = 0; i < ndirs; i++)
419 {
420 const StringFormatDirective &fdir = fdirs[i];
421 argmaxref = std::max (argmaxref, size_t (fdir.value_index));
422 argmaxref = std::max (argmaxref, size_t (fdir.width_index));
423 argmaxref = std::max (argmaxref, size_t (fdir.precision_index));
424 }
425 if (argmaxref > N)
426 return format_error ("too few arguments for format", format, 0);
427 if (argmaxref < N)
428 return format_error ("too many arguments for format", format, 0);
429 // format pieces
430 std::string result;
431 p = format;
432 for (size_t i = 0; i <= ndirs; i++)
433 {
434 const StringFormatDirective &fdir = fdirs[i];
435 result += std::string (p, fdir.start - (p - format));
436 if (fdir.conversion)
437 result += fdir.render_directive (N, args);
438 p = format + fdir.end;
439 }
440 return result;
441}
442
443} // Impl
444
445} // Ase
446
447// == Testing ==
448namespace { // Anon
449struct UncopyablePoint {
450 double x, y;
451 friend inline std::ostream&
452 operator<< (std::ostream &s, const UncopyablePoint &p) { return s << "{" << p.x << ";" << p.y << "}"; }
453 UncopyablePoint (double _x, double _y) : x (_x), y (_y) {}
454 ASE_CLASS_NON_COPYABLE (UncopyablePoint);
455};
456
457TEST_INTEGRITY (ase_string_format);
458static void
459ase_string_format()
460{
461 using namespace Ase;
462 std::atomic<bool> boolean = 1;
463 // string_format
464 TCMP (string_format ("%d", bool (1)), ==, "1");
465 TCMP (string_format ("%d", char (-17)), ==, "-17");
466 TCMP (string_format ("%s", nullptr), ==, "(null)");
467 TCMP (string_format ("%s", boolean), ==, "1");
468 TCMP (string_format ("%s", (char*) "FOO"), ==, "FOO");
469 TCMP (string_format ("%s", (void*) "FOO"), ==, "");
470 TCMP (string_format ("%d %s", -9223372036854775808uLL, "FOO"), ==, "-9223372036854775808 FOO");
471 TCMP (string_format ("0x%08x", 0xc0ffee), ==, "0x00c0ffee");
472 enum { TEST17 = 17 };
473 TCMP (string_format ("%g %d", 0.5, TEST17), ==, "0.5 17");
474 static_assert (TEST17 == 17, "!");
475 TCMP (string_format ("Only %c%%", '3'), ==, "Only 3%");
476 // ostream tests
477 UncopyablePoint point { 1, 2 };
478 TCMP (string_format ("%s", point), ==, "{1;2}");
479 TCMP (string_format ("%s+%s+%s", point, point, point), ==, "{1;2}+{1;2}+{1;2}");
480 String sfoo ("foo");
481 typedef char MutableChar;
482 MutableChar *foo = &sfoo[0];
483 TCMP (string_format ("%s", foo), ==, "foo");
484 // test robustness for arcane/rarely-used width modifiers
485 const char *arcane_format = "| %qd %Zd %LF |";
486 TCMP (string_format (arcane_format, (long long) 1234, size_t (4321), (long double) 1234.), ==, "| 1234 4321 1234.000000 |");
487 TCMP (string_format ("- %C - %lc -", long ('X'), long ('x')), ==, "- X - x -");
488 // TCMP (string_format ("+ %S +", (wchar_t*) "\1\1\1\1\0\0\0\0"), ==, "+ \1\1\1\1 +");
489}
490
491} // Anon
#define ASE_CLASS_NON_COPYABLE(ClassName)
Delete copy ctor and assignment operator.
Definition cxxaux.hh:111
fprintf
T format(T... args)
free
getenv
T index(T... args)
#define TEST_INTEGRITY(FUNC)
Register func as an integrity test.
Definition internal.hh:79
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