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