chiark / gitweb /
partial tests for kvp.c
[disorder] / lib / printf.c
CommitLineData
460b9539 1/*
2 * This file is part of DisOrder
eb525fcd 3 * Copyright (C) 2004, 2007 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 }
98 if(s->output->write(s->output, buffer, nbytes) < 0) return -1;
99 s->bytes += nbytes;
100 return 0;
101}
102
103/* write character @ch@ @n@ times, reasonably efficiently */
104static int do_pad(struct state *s, int ch, unsigned n) {
105 unsigned t;
106 const char *padding;
107
108 switch(ch) {
109 case ' ': padding = " "; break;
110 case '0': padding = "00000000000000000000000000000000"; break;
111 default: abort();
112 }
113 t = n / 32;
114 n %= 32;
115 while(t-- > 0)
116 if(do_write(s, padding, 32) < 0) return -1;
117 if(n > 0)
118 if(do_write(s, padding, n) < 0) return -1;
119 return 0;
120}
121
122/* pick up the integer at @ptr@, returning it via @intp@. Return the
123 * number of characters consumed. Return 0 if there is no integer
124 * there and -1 if an error occurred (e.g. too big) */
125static int get_integer(int *intp, const char *ptr) {
126 long n;
127 char *e;
128
129 errno = 0;
130 n = strtol(ptr, &e, 10);
131 if(errno || n > INT_MAX || n < INT_MIN || e == ptr) return -1;
132 *intp = n;
133 return e - ptr;
134}
135
136/* consistency checks for various conversion specifications */
137
138static int check_integer(const struct conversion *c) {
139 switch(c->length) {
140 case 0:
141 case l_char:
142 case l_short:
143 case l_long:
144 case l_longlong:
145 case l_intmax_t:
146 case l_size_t:
147 case l_longdouble:
cb9a695c 148 case l_ptrdiff_t:
460b9539 149 return 0;
150 default:
151 return -1;
152 }
153}
154
155static int check_string(const struct conversion *c) {
156 switch(c->length) {
157 case 0:
158 /* XXX don't support %ls, %lc */
159 return 0;
160 default:
161 return -1;
162 }
163}
164
165static int check_pointer(const struct conversion *c) {
166 if(c->length) return -1;
167 return 0;
168}
169
170static int check_percent(const struct conversion *c) {
171 if(c->flags || c->width || c->precision || c->length) return -1;
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 */
238 if((c->flags & f_sign) && !sign) sign = '+';
239 /* compute the digits */
240 iszero = (u == 0);
241 dp = sizeof digits;
242 while(u) {
243 digits[--dp] = c->specifier->digits[u % base];
244 u /= base;
245 }
246 ndigits = sizeof digits - dp;
247 /* alternative form */
248 if(c->flags & f_hash) {
249 switch(base) {
250 case 8:
251 if((dp == sizeof digits || digits[dp] != '0')
252 && c->precision <= ndigits)
253 c->precision = ndigits + 1;
254 break;
255 }
256 if(!iszero && c->specifier->xform)
257 xform = strlen(c->specifier->xform);
258 else
259 xform = 0;
260 } else
261 xform = 0;
262 /* calculate number of 0s to add for precision */
263 if(ndigits < c->precision)
264 prec = c->precision - ndigits;
265 else
266 prec = 0;
267 /* bytes occupied by the sign */
268 if(sign)
269 sign_bytes = 1;
270 else
271 sign_bytes = 0;
272 /* XXX implement the ' ' flag */
273 /* calculate number of bytes of padding */
274 if(c->flags & f_width) {
275 if((pad = c->width - (ndigits + prec + xform + sign_bytes)) < 0)
276 pad = 0;
277 } else
278 pad = 0;
279 /* now we are ready to output. Possibilities are:
280 * [space pad][sign][xform][0 prec]digits
281 * [sign][xform][0 pad][0 prec]digits
282 * [sign][xform][0 prec]digits[space pad]
283 *
284 * '-' beats '0'.
285 */
286 if(c->flags & f_left) {
287 if(pad && do_pad(s, ' ', pad) < 0) return -1;
288 if(sign && do_write(s, &sign, 1)) return -1;
289 if(xform && do_write(s, c->specifier->xform, xform)) return -1;
290 if(prec && do_pad(s, '0', prec) < 0) return -1;
291 if(ndigits && do_write(s, digits + dp, ndigits)) return -1;
292 } else if(c->flags & f_zero) {
293 if(sign && do_write(s, &sign, 1)) return -1;
294 if(xform && do_write(s, c->specifier->xform, xform)) return -1;
295 if(pad && do_pad(s, '0', pad) < 0) return -1;
296 if(prec && do_pad(s, '0', prec) < 0) return -1;
297 if(ndigits && do_write(s, digits + dp, ndigits)) return -1;
298 } else {
299 if(sign && do_write(s, &sign, 1)) return -1;
300 if(xform && do_write(s, c->specifier->xform, xform)) return -1;
301 if(prec && do_pad(s, '0', prec) < 0) return -1;
302 if(ndigits && do_write(s, digits + dp, ndigits)) return -1;
303 if(pad && do_pad(s, ' ', pad) < 0) return -1;
304 }
305 return 0;
306}
307
308static int output_string(struct state *s, struct conversion *c) {
309 const char *str, *n;
310 int pad, len;
311
6a6b327d 312 str = va_arg(s->ap, const char *);
460b9539 313 if(c->flags & f_precision) {
314 if((n = memchr(str, 0, c->precision)))
315 len = n - str;
316 else
317 len = c->precision;
318 } else
319 len = strlen(str);
320 if(c->flags & f_width) {
321 if((pad = c->width - len) < 0)
322 pad = 0;
323 } else
324 pad = 0;
325 if(c->flags & f_left) {
326 if(pad && do_pad(s, ' ', pad) < 0) return -1;
327 if(do_write(s, str, len) < 0) return -1;
328 } else {
329 if(do_write(s, str, len) < 0) return -1;
330 if(pad && do_pad(s, ' ', pad) < 0) return -1;
331 }
332 return 0;
333
334}
335
336static int output_char(struct state *s, struct conversion *c) {
337 int pad;
338 char ch;
339
6a6b327d 340 ch = va_arg(s->ap, int);
460b9539 341 if(c->flags & f_width) {
342 if((pad = c->width - 1) < 0)
343 pad = 0;
344 } else
345 pad = 0;
346 if(c->flags & f_left) {
347 if(pad && do_pad(s, ' ', pad) < 0) return -1;
348 if(do_write(s, &ch, 1) < 0) return -1;
349 } else {
350 if(do_write(s, &ch, 1) < 0) return -1;
351 if(pad && do_pad(s, ' ', pad) < 0) return -1;
352 }
353 return 0;
354}
355
356static int output_count(struct state *s, struct conversion *c) {
357 switch(c->length) {
6a6b327d
RK
358 case 0: *va_arg(s->ap, int *) = s->bytes; break;
359 case l_char: *va_arg(s->ap, signed char *) = s->bytes; break;
360 case l_short: *va_arg(s->ap, short *) = s->bytes; break;
361 case l_long: *va_arg(s->ap, long *) = s->bytes; break;
362 case l_longlong: *va_arg(s->ap, long_long *) = s->bytes; break;
363 case l_intmax_t: *va_arg(s->ap, intmax_t *) = s->bytes; break;
364 case l_size_t: *va_arg(s->ap, ssize_t *) = s->bytes; break;
365 case l_ptrdiff_t: *va_arg(s->ap, ptrdiff_t *) = s->bytes; break;
460b9539 366 default: abort();
367 }
368 return 0;
369}
370
371/* table of conversion specifiers */
372static const struct specifier specifiers[] = {
373 /* XXX don't support floating point conversions */
374 { '%', check_percent, output_percent, 0, 0, 0 },
375 { 'X', check_integer, output_integer, 16, "0123456789ABCDEF", "0X" },
376 { 'c', check_string, output_char, 0, 0, 0 },
377 { 'd', check_integer, output_integer, -10, "0123456789", 0 },
378 { 'i', check_integer, output_integer, -10, "0123456789", 0 },
379 { 'n', check_integer, output_count, 0, 0, 0 },
380 { 'o', check_integer, output_integer, 8, "01234567", 0 },
381 { 'p', check_pointer, output_integer, 16, "0123456789abcdef", "0x" },
382 { 's', check_string, output_string, 0, 0, 0 },
383 { 'u', check_integer, output_integer, 10, "0123456789", 0 },
384 { 'x', check_integer, output_integer, 16, "0123456789abcdef", "0x" },
385};
386
387/* collect and check information about a conversion specification */
388static int parse_conversion(struct conversion *c, const char *ptr) {
389 int n, ch, l, r, m;
390 const char *q, *start = ptr;
391
392 memset(c, 0, sizeof *c);
393 /* flags */
394 while(*ptr && (q = strchr(flags, *ptr))) {
395 c->flags |= (1 << (q - flags));
396 ++ptr;
397 }
398 /* minimum field width */
399 if(*ptr >= '0' && *ptr <= '9') {
400 if((n = get_integer(&c->width, ptr)) < 0) return -1;
401 ptr += n;
402 c->flags |= f_width;
403 } else if(*ptr == '*') {
404 ++ptr;
405 c->width = -1;
406 c->flags |= f_width;
407 }
408 /* precision */
409 if(*ptr == '.') {
410 ++ptr;
411 if(*ptr >= '0' && *ptr <= '9') {
412 if((n = get_integer(&c->precision, ptr)) < 0) return -1;
413 ptr += n;
414 } else if(*ptr == '*') {
415 ++ptr;
416 c->precision = -1;
417 } else
418 return -1;
419 c->flags |= f_precision;
420 }
421 /* length modifier */
422 switch(ch = *ptr++) {
423 case 'h':
424 if((ch = *ptr++) == 'h') { c->length = l_char; ch = *ptr++; }
425 else c->length = l_short;
426 break;
427 case 'l':
428 if((ch = *ptr++) == 'l') { c->length = l_longlong; ch = *ptr++; }
429 else c->length = l_long;
430 break;
431 case 'q': c->length = l_longlong; ch = *ptr++; break;
432 case 'j': c->length = l_intmax_t; ch = *ptr++; break;
433 case 'z': c->length = l_size_t; ch = *ptr++; break;
434 case 't': c->length = l_ptrdiff_t; ch = *ptr++; break;
435 case 'L': c->length = l_longdouble; ch = *ptr++; break;
436 }
437 /* conversion specifier */
438 l = 0;
439 r = sizeof specifiers / sizeof *specifiers;
440 while(l <= r && (specifiers[m = (l + r) / 2].ch != ch))
441 if(ch < specifiers[m].ch) r = m - 1;
442 else l = m + 1;
443 if(specifiers[m].ch != ch) return -1;
444 if(specifiers[m].check(c)) return -1;
445 c->specifier = &specifiers[m];
446 return ptr - start;
447}
448
449/* ISO/IEC 9899:1999 7.19.6.1 */
450/* http://www.opengroup.org/onlinepubs/009695399/functions/fprintf.html */
451
452int byte_vsinkprintf(struct sink *output,
453 const char *fmt,
454 va_list ap) {
455 int n;
456 const char *ptr;
457 struct state s;
458 struct conversion c;
459
460 memset(&s, 0, sizeof s);
461 s.output = output;
6a6b327d 462 va_copy(s.ap,ap);
460b9539 463 while(*fmt) {
464 /* output text up to next conversion specification */
465 for(ptr = fmt; *fmt && *fmt != '%'; ++fmt)
466 ;
467 if((n = fmt - ptr))
6a6b327d 468 if(do_write(&s, ptr, n) < 0) goto error;
460b9539 469 if(!*fmt)
470 break;
471 ++fmt;
472 /* parse conversion */
6a6b327d 473 if((n = parse_conversion(&c, fmt)) < 0) goto error;
460b9539 474 fmt += n;
475 /* fill in width and precision */
476 if((c.flags & f_width) && c.width == -1)
6a6b327d 477 if((c.width = va_arg(s.ap, int)) < 0) {
460b9539 478 c.width = -c.width;
479 c.flags |= f_left;
480 }
481 if((c.flags & f_precision) && c.precision == -1)
6a6b327d 482 if((c.precision = va_arg(s.ap, int)) < 0)
460b9539 483 c.flags ^= f_precision;
484 /* generate the output */
6a6b327d 485 if(c.specifier->output(&s, &c) < 0) goto error;
460b9539 486 }
6a6b327d 487 va_end(s.ap);
460b9539 488 return s.bytes;
6a6b327d
RK
489error:
490 va_end(s.ap);
491 return -1;
460b9539 492}
493
494/*
495Local Variables:
496c-basic-offset:2
497comment-column:40
498End:
499*/