chiark / gitweb /
disobedience, playrtp: Have `playrtp' handle volume control.
[disorder] / lib / printf.c
CommitLineData
460b9539 1/*
2 * This file is part of DisOrder
681cb9fb 3 * Copyright (C) 2004, 2007, 2008 Richard Kettlewell
460b9539 4 *
e7eb3a27 5 * This program is free software: you can redistribute it and/or modify
460b9539 6 * it under the terms of the GNU General Public License as published by
e7eb3a27 7 * the Free Software Foundation, either version 3 of the License, or
460b9539 8 * (at your option) any later version.
e7eb3a27
RK
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
460b9539 15 * You should have received a copy of the GNU General Public License
e7eb3a27 16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
460b9539 17 */
132a5a4a
RK
18/** @file lib/printf.c
19 * @brief UTF-8 *printf workalike (core)
20 */
460b9539 21
22#define NO_MEMORY_ALLOCATION
23/* because byte_snprintf used from log.c */
24
05b75f8d 25#include "common.h"
460b9539 26
460b9539 27#include <stdarg.h>
460b9539 28#include <errno.h>
460b9539 29#include <stddef.h>
30
31#include "printf.h"
902b9f3f 32#include "log.h"
460b9539 33#include "sink.h"
e3426f7b 34#include "vacopy.h"
460b9539 35
00682707
RK
36/** @brief Flags from a converstion specification
37 *
38 * Order significant!
39 */
460b9539 40enum flags {
41 f_thousands = 1,
42 f_left = 2,
43 f_sign = 4,
44 f_space = 8,
45 f_hash = 16,
46 f_zero = 32,
47 f_width = 256,
48 f_precision = 512
49};
50
00682707 51/** @brief Possible lengths of a conversion specification */
460b9539 52enum lengths {
53 l_char = 1,
54 l_short,
55 l_long,
56 l_longlong,
57 l_size_t,
58 l_intmax_t,
59 l_ptrdiff_t,
60 l_longdouble
61};
62
63struct conversion;
64
00682707 65/** @brief Formatter state */
460b9539 66struct state {
00682707 67 /** @brief Output stream */
460b9539 68 struct sink *output;
00682707
RK
69
70 /** @brief Number of bytes written */
460b9539 71 int bytes;
00682707
RK
72
73 /** @brief Argument list */
6a6b327d 74 va_list ap;
460b9539 75};
76
00682707 77/** @brief Definition of a conversion specifier */
460b9539 78struct specifier {
00682707 79 /** @brief Defining character ('d', 's' etc) */
460b9539 80 int ch;
00682707
RK
81
82 /** @brief Consistency check
83 * @param c Conversion being processed
84 * @return 0 if OK, -1 on error
85 */
460b9539 86 int (*check)(const struct conversion *c);
00682707
RK
87
88 /** @brief Generate output
89 * @param s Formatter state
90 * @param c Conversion being processed
91 * @return 0 on success, -1 on error
92 */
460b9539 93 int (*output)(struct state *s, struct conversion *c);
00682707
RK
94
95 /** @brief Number base */
460b9539 96 int base;
00682707
RK
97
98 /** @brief Digit set */
460b9539 99 const char *digits;
00682707
RK
100
101 /** @brief Alternative-form prefix */
460b9539 102 const char *xform;
103};
104
00682707 105/** @brief One conversion specified as it's handled */
460b9539 106struct conversion {
00682707 107 /** @brief Flags in this conversion */
460b9539 108 unsigned flags;
00682707
RK
109
110 /** @brief Field width (if @ref f_width) */
460b9539 111 int width;
00682707
RK
112
113 /** @brief Precision (if @ref f_precision) */
460b9539 114 int precision;
00682707
RK
115
116 /** @brief Length modifier or 0 */
460b9539 117 int length;
00682707
RK
118
119 /** @brief Specifier used */
460b9539 120 const struct specifier *specifier;
121};
122
00682707 123/** @brief Flag characters (order significant!) */
460b9539 124static const char flags[] = "'-+ #0";
125
126/* write @nbytes@ to the output. Return -1 on error, 0 on success.
127 * Keeps track of the number of bytes written. */
128static int do_write(struct state *s,
129 const void *buffer,
130 int nbytes) {
131 if(s->bytes > INT_MAX - nbytes) {
132#ifdef EOVERFLOW
133 errno = EOVERFLOW;
134#endif
135 return -1;
136 }
681cb9fb
RK
137 if(s->output->write(s->output, buffer, nbytes) < 0)
138 return -1;
460b9539 139 s->bytes += nbytes;
140 return 0;
141}
142
143/* write character @ch@ @n@ times, reasonably efficiently */
144static int do_pad(struct state *s, int ch, unsigned n) {
145 unsigned t;
146 const char *padding;
147
148 switch(ch) {
149 case ' ': padding = " "; break;
150 case '0': padding = "00000000000000000000000000000000"; break;
151 default: abort();
152 }
153 t = n / 32;
154 n %= 32;
155 while(t-- > 0)
681cb9fb
RK
156 if(do_write(s, padding, 32) < 0)
157 return -1;
460b9539 158 if(n > 0)
681cb9fb
RK
159 if(do_write(s, padding, n) < 0)
160 return -1;
460b9539 161 return 0;
162}
163
164/* pick up the integer at @ptr@, returning it via @intp@. Return the
165 * number of characters consumed. Return 0 if there is no integer
166 * there and -1 if an error occurred (e.g. too big) */
167static int get_integer(int *intp, const char *ptr) {
168 long n;
169 char *e;
170
171 errno = 0;
172 n = strtol(ptr, &e, 10);
681cb9fb
RK
173 if(errno || n > INT_MAX || n < INT_MIN || e == ptr)
174 return -1;
460b9539 175 *intp = n;
176 return e - ptr;
177}
178
179/* consistency checks for various conversion specifications */
180
181static int check_integer(const struct conversion *c) {
182 switch(c->length) {
183 case 0:
184 case l_char:
185 case l_short:
186 case l_long:
187 case l_longlong:
188 case l_intmax_t:
189 case l_size_t:
190 case l_longdouble:
cb9a695c 191 case l_ptrdiff_t:
460b9539 192 return 0;
193 default:
194 return -1;
195 }
196}
197
198static int check_string(const struct conversion *c) {
199 switch(c->length) {
200 case 0:
201 /* XXX don't support %ls, %lc */
202 return 0;
203 default:
204 return -1;
205 }
206}
207
208static int check_pointer(const struct conversion *c) {
681cb9fb
RK
209 if(c->length)
210 return -1;
460b9539 211 return 0;
212}
213
214static int check_percent(const struct conversion *c) {
681cb9fb
RK
215 if(c->flags || c->width || c->precision || c->length)
216 return -1;
460b9539 217 return 0;
218}
219
220/* output functions for various conversion specifications */
221
222static int output_percent(struct state *s,
223 struct conversion attribute((unused)) *c) {
224 return do_write(s, "%", 1);
225}
226
227static int output_integer(struct state *s, struct conversion *c) {
228 uintmax_t u;
229 intmax_t l;
230 char sign;
231 int base, dp, iszero, ndigits, prec, xform, sign_bytes, pad;
232 char digits[CHAR_BIT * sizeof (uintmax_t)]; /* overestimate */
233
234 switch(c->specifier->ch) {
235 default:
236 if(c->specifier->base < 0) {
237 switch(c->length) {
6a6b327d
RK
238 case 0: l = va_arg(s->ap, int); break;
239 case l_char: l = (signed char)va_arg(s->ap, int); break;
240 case l_short: l = (short)va_arg(s->ap, int); break;
241 case l_long: l = va_arg(s->ap, long); break;
242 case l_longlong: l = va_arg(s->ap, long_long); break;
243 case l_intmax_t: l = va_arg(s->ap, intmax_t); break;
244 case l_size_t: l = va_arg(s->ap, ssize_t); break;
245 case l_ptrdiff_t: l = va_arg(s->ap, ptrdiff_t); break;
460b9539 246 default: abort();
247 }
248 base = -c->specifier->base;
249 if(l < 0) {
250 u = -l;
251 sign = '-';
252 } else {
253 u = l;
254 sign = 0;
255 }
256 } else {
257 switch(c->length) {
6a6b327d
RK
258 case 0: u = va_arg(s->ap, unsigned int); break;
259 case l_char: u = (unsigned char)va_arg(s->ap, unsigned int); break;
260 case l_short: u = (unsigned short)va_arg(s->ap, unsigned int); break;
261 case l_long: u = va_arg(s->ap, unsigned long); break;
262 case l_longlong: u = va_arg(s->ap, u_long_long); break;
263 case l_intmax_t: u = va_arg(s->ap, uintmax_t); break;
264 case l_size_t: u = va_arg(s->ap, size_t); break;
265 case l_ptrdiff_t: u = va_arg(s->ap, ptrdiff_t); break;
460b9539 266 default: abort();
267 }
268 base = c->specifier->base;
269 sign = 0;
270 }
271 break;
272 case 'p':
6a6b327d 273 u = (uintptr_t)va_arg(s->ap, void *);
460b9539 274 c->flags |= f_hash;
275 base = c->specifier->base;
276 sign = 0;
277 break;
278 }
279 /* default precision */
280 if(!(c->flags & f_precision))
281 c->precision = 1;
282 /* enforce sign */
681cb9fb
RK
283 if((c->flags & f_sign) && !sign)
284 sign = '+';
460b9539 285 /* compute the digits */
286 iszero = (u == 0);
287 dp = sizeof digits;
288 while(u) {
289 digits[--dp] = c->specifier->digits[u % base];
290 u /= base;
291 }
292 ndigits = sizeof digits - dp;
293 /* alternative form */
294 if(c->flags & f_hash) {
295 switch(base) {
296 case 8:
297 if((dp == sizeof digits || digits[dp] != '0')
298 && c->precision <= ndigits)
299 c->precision = ndigits + 1;
300 break;
301 }
302 if(!iszero && c->specifier->xform)
303 xform = strlen(c->specifier->xform);
304 else
305 xform = 0;
306 } else
307 xform = 0;
308 /* calculate number of 0s to add for precision */
309 if(ndigits < c->precision)
310 prec = c->precision - ndigits;
311 else
312 prec = 0;
313 /* bytes occupied by the sign */
314 if(sign)
315 sign_bytes = 1;
316 else
317 sign_bytes = 0;
318 /* XXX implement the ' ' flag */
319 /* calculate number of bytes of padding */
320 if(c->flags & f_width) {
321 if((pad = c->width - (ndigits + prec + xform + sign_bytes)) < 0)
322 pad = 0;
323 } else
324 pad = 0;
325 /* now we are ready to output. Possibilities are:
326 * [space pad][sign][xform][0 prec]digits
327 * [sign][xform][0 pad][0 prec]digits
328 * [sign][xform][0 prec]digits[space pad]
329 *
330 * '-' beats '0'.
331 */
332 if(c->flags & f_left) {
681cb9fb
RK
333 if(sign && do_write(s, &sign, 1))
334 return -1;
335 if(xform && do_write(s, c->specifier->xform, xform))
336 return -1;
337 if(prec && do_pad(s, '0', prec) < 0)
338 return -1;
339 if(ndigits && do_write(s, digits + dp, ndigits))
340 return -1;
341 if(pad && do_pad(s, ' ', pad) < 0)
342 return -1;
460b9539 343 } else if(c->flags & f_zero) {
681cb9fb
RK
344 if(sign && do_write(s, &sign, 1))
345 return -1;
346 if(xform && do_write(s, c->specifier->xform, xform))
347 return -1;
348 if(pad && do_pad(s, '0', pad) < 0)
349 return -1;
350 if(prec && do_pad(s, '0', prec) < 0)
351 return -1;
352 if(ndigits && do_write(s, digits + dp, ndigits))
353 return -1;
460b9539 354 } else {
681cb9fb
RK
355 if(pad && do_pad(s, ' ', pad) < 0)
356 return -1;
357 if(sign && do_write(s, &sign, 1))
358 return -1;
359 if(xform && do_write(s, c->specifier->xform, xform))
360 return -1;
361 if(prec && do_pad(s, '0', prec) < 0)
362 return -1;
363 if(ndigits && do_write(s, digits + dp, ndigits))
364 return -1;
460b9539 365 }
366 return 0;
367}
368
369static int output_string(struct state *s, struct conversion *c) {
370 const char *str, *n;
371 int pad, len;
372
6a6b327d 373 str = va_arg(s->ap, const char *);
460b9539 374 if(c->flags & f_precision) {
375 if((n = memchr(str, 0, c->precision)))
376 len = n - str;
377 else
378 len = c->precision;
379 } else
380 len = strlen(str);
381 if(c->flags & f_width) {
382 if((pad = c->width - len) < 0)
383 pad = 0;
384 } else
385 pad = 0;
386 if(c->flags & f_left) {
681cb9fb
RK
387 if(do_write(s, str, len) < 0)
388 return -1;
389 if(pad && do_pad(s, ' ', pad) < 0)
390 return -1;
460b9539 391 } else {
681cb9fb
RK
392 if(pad && do_pad(s, ' ', pad) < 0)
393 return -1;
394 if(do_write(s, str, len) < 0)
395 return -1;
460b9539 396 }
397 return 0;
398
399}
400
401static int output_char(struct state *s, struct conversion *c) {
402 int pad;
403 char ch;
404
6a6b327d 405 ch = va_arg(s->ap, int);
460b9539 406 if(c->flags & f_width) {
407 if((pad = c->width - 1) < 0)
408 pad = 0;
409 } else
410 pad = 0;
411 if(c->flags & f_left) {
681cb9fb
RK
412 if(do_write(s, &ch, 1) < 0)
413 return -1;
414 if(pad && do_pad(s, ' ', pad) < 0)
415 return -1;
460b9539 416 } else {
681cb9fb
RK
417 if(pad && do_pad(s, ' ', pad) < 0)
418 return -1;
419 if(do_write(s, &ch, 1) < 0)
420 return -1;
460b9539 421 }
422 return 0;
423}
424
425static int output_count(struct state *s, struct conversion *c) {
426 switch(c->length) {
6a6b327d
RK
427 case 0: *va_arg(s->ap, int *) = s->bytes; break;
428 case l_char: *va_arg(s->ap, signed char *) = s->bytes; break;
429 case l_short: *va_arg(s->ap, short *) = s->bytes; break;
430 case l_long: *va_arg(s->ap, long *) = s->bytes; break;
431 case l_longlong: *va_arg(s->ap, long_long *) = s->bytes; break;
432 case l_intmax_t: *va_arg(s->ap, intmax_t *) = s->bytes; break;
433 case l_size_t: *va_arg(s->ap, ssize_t *) = s->bytes; break;
434 case l_ptrdiff_t: *va_arg(s->ap, ptrdiff_t *) = s->bytes; break;
460b9539 435 default: abort();
436 }
437 return 0;
438}
439
440/* table of conversion specifiers */
441static const struct specifier specifiers[] = {
442 /* XXX don't support floating point conversions */
443 { '%', check_percent, output_percent, 0, 0, 0 },
444 { 'X', check_integer, output_integer, 16, "0123456789ABCDEF", "0X" },
445 { 'c', check_string, output_char, 0, 0, 0 },
446 { 'd', check_integer, output_integer, -10, "0123456789", 0 },
447 { 'i', check_integer, output_integer, -10, "0123456789", 0 },
448 { 'n', check_integer, output_count, 0, 0, 0 },
449 { 'o', check_integer, output_integer, 8, "01234567", 0 },
450 { 'p', check_pointer, output_integer, 16, "0123456789abcdef", "0x" },
451 { 's', check_string, output_string, 0, 0, 0 },
452 { 'u', check_integer, output_integer, 10, "0123456789", 0 },
453 { 'x', check_integer, output_integer, 16, "0123456789abcdef", "0x" },
454};
455
456/* collect and check information about a conversion specification */
457static int parse_conversion(struct conversion *c, const char *ptr) {
458 int n, ch, l, r, m;
459 const char *q, *start = ptr;
460
461 memset(c, 0, sizeof *c);
462 /* flags */
463 while(*ptr && (q = strchr(flags, *ptr))) {
464 c->flags |= (1 << (q - flags));
465 ++ptr;
466 }
467 /* minimum field width */
468 if(*ptr >= '0' && *ptr <= '9') {
681cb9fb
RK
469 if((n = get_integer(&c->width, ptr)) < 0)
470 return -1;
460b9539 471 ptr += n;
472 c->flags |= f_width;
473 } else if(*ptr == '*') {
474 ++ptr;
475 c->width = -1;
476 c->flags |= f_width;
477 }
478 /* precision */
479 if(*ptr == '.') {
480 ++ptr;
481 if(*ptr >= '0' && *ptr <= '9') {
681cb9fb
RK
482 if((n = get_integer(&c->precision, ptr)) < 0)
483 return -1;
460b9539 484 ptr += n;
485 } else if(*ptr == '*') {
486 ++ptr;
487 c->precision = -1;
488 } else
0c740e59 489 c->precision = 0;
460b9539 490 c->flags |= f_precision;
491 }
492 /* length modifier */
493 switch(ch = *ptr++) {
494 case 'h':
681cb9fb
RK
495 if((ch = *ptr++) == 'h') {
496 c->length = l_char;
497 ch = *ptr++;
498 }
499 else
500 c->length = l_short;
460b9539 501 break;
502 case 'l':
681cb9fb
RK
503 if((ch = *ptr++) == 'l') {
504 c->length = l_longlong;
505 ch = *ptr++;
506 }
507 else
508 c->length = l_long;
460b9539 509 break;
510 case 'q': c->length = l_longlong; ch = *ptr++; break;
511 case 'j': c->length = l_intmax_t; ch = *ptr++; break;
512 case 'z': c->length = l_size_t; ch = *ptr++; break;
513 case 't': c->length = l_ptrdiff_t; ch = *ptr++; break;
514 case 'L': c->length = l_longdouble; ch = *ptr++; break;
515 }
516 /* conversion specifier */
517 l = 0;
518 r = sizeof specifiers / sizeof *specifiers;
519 while(l <= r && (specifiers[m = (l + r) / 2].ch != ch))
681cb9fb
RK
520 if(ch < specifiers[m].ch)
521 r = m - 1;
522 else
523 l = m + 1;
524 if(specifiers[m].ch != ch)
525 return -1;
526 if(specifiers[m].check(c))
527 return -1;
460b9539 528 c->specifier = &specifiers[m];
529 return ptr - start;
530}
531
532/* ISO/IEC 9899:1999 7.19.6.1 */
533/* http://www.opengroup.org/onlinepubs/009695399/functions/fprintf.html */
534
535int byte_vsinkprintf(struct sink *output,
536 const char *fmt,
537 va_list ap) {
538 int n;
539 const char *ptr;
540 struct state s;
541 struct conversion c;
542
543 memset(&s, 0, sizeof s);
544 s.output = output;
6a6b327d 545 va_copy(s.ap,ap);
460b9539 546 while(*fmt) {
547 /* output text up to next conversion specification */
548 for(ptr = fmt; *fmt && *fmt != '%'; ++fmt)
549 ;
550 if((n = fmt - ptr))
681cb9fb
RK
551 if(do_write(&s, ptr, n) < 0)
552 goto error;
460b9539 553 if(!*fmt)
554 break;
555 ++fmt;
556 /* parse conversion */
681cb9fb
RK
557 if((n = parse_conversion(&c, fmt)) < 0)
558 goto error;
460b9539 559 fmt += n;
560 /* fill in width and precision */
561 if((c.flags & f_width) && c.width == -1)
6a6b327d 562 if((c.width = va_arg(s.ap, int)) < 0) {
460b9539 563 c.width = -c.width;
564 c.flags |= f_left;
565 }
566 if((c.flags & f_precision) && c.precision == -1)
6a6b327d 567 if((c.precision = va_arg(s.ap, int)) < 0)
460b9539 568 c.flags ^= f_precision;
569 /* generate the output */
681cb9fb
RK
570 if(c.specifier->output(&s, &c) < 0)
571 goto error;
460b9539 572 }
6a6b327d 573 va_end(s.ap);
460b9539 574 return s.bytes;
6a6b327d
RK
575error:
576 va_end(s.ap);
577 return -1;
460b9539 578}
579
681cb9fb
RK
580int byte_sinkprintf(struct sink *output, const char *fmt, ...) {
581 int n;
582 va_list ap;
583
584 va_start(ap, fmt);
585 n = byte_vsinkprintf(output, fmt, ap);
586 va_end(ap);
587 return n;
588}
589
460b9539 590/*
591Local Variables:
592c-basic-offset:2
593comment-column:40
594End:
595*/