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