xref: /plan9/sys/src/libstdio/vfprintf.c (revision 219b2ee8daee37f4aad58d63f21287faa8e4ffdc)
1 /*
2  * pANS stdio -- vfprintf
3  */
4 #include "iolib.h"
5 /*
6  * Leading flags
7  */
8 #define	SPACE	1		/* ' ' prepend space if no sign printed */
9 #define	ALT	2		/* '#' use alternate conversion */
10 #define	SIGN	4		/* '+' prepend sign, even if positive */
11 #define	LEFT	8		/* '-' left-justify */
12 #define	ZPAD	16		/* '0' zero-pad */
13 /*
14  * Trailing flags
15  */
16 #define	SHORT	32		/* 'h' convert a short integer */
17 #define	LONG	64		/* 'l' convert a long integer */
18 #define	LDBL	128		/* 'L' convert a long double */
19 #define	PTR	256		/*     convert a void * (%p) */
20 
21 static int lflag[] = {	/* leading flags */
22 0,	0,	0,	0,	0,	0,	0,	0,	/* ^@ ^A ^B ^C ^D ^E ^F ^G */
23 0,	0,	0,	0,	0,	0,	0,	0,	/* ^H ^I ^J ^K ^L ^M ^N ^O */
24 0,	0,	0,	0,	0,	0,	0,	0,	/* ^P ^Q ^R ^S ^T ^U ^V ^W */
25 0,	0,	0,	0,	0,	0,	0,	0,	/* ^X ^Y ^Z ^[ ^\ ^] ^^ ^_ */
26 SPACE,	0,	0,	ALT,	0,	0,	0,	0,	/* sp  !  "  #  $  %  &  ' */
27 0,	0,	0,	SIGN,	0,	LEFT,	0,	0,	/*  (  )  *  +  ,  -  .  / */
28 ZPAD,	0,	0,	0,	0,	0,	0,	0,	/*  0  1  2  3  4  5  6  7 */
29 0,	0,	0,	0,	0,	0,	0,	0,	/*  8  9  :  ;  <  =  >  ? */
30 0,	0,	0,	0,	0,	0,	0,	0,	/*  @  A  B  C  D  E  F  G */
31 0,	0,	0,	0,	0,	0,	0,	0,	/*  H  I  J  K  L  M  N  O */
32 0,	0,	0,	0,	0,	0,	0,	0,	/*  P  Q  R  S  T  U  V  W */
33 0,	0,	0,	0,	0,	0,	0,	0,	/*  X  Y  Z  [  \  ]  ^  _ */
34 0,	0,	0,	0,	0,	0,	0,	0,	/*  `  a  b  c  d  e  f  g */
35 0,	0,	0,	0,	0,	0,	0,	0,	/*  h  i  j  k  l  m  n  o */
36 0,	0,	0,	0,	0,	0,	0,	0,	/*  p  q  r  s  t  u  v  w */
37 0,	0,	0,	0,	0,	0,	0,	0,	/*  x  y  z  {  |  }  ~ ^? */
38 
39 0,	0,	0,	0,	0,	0,	0,	0,
40 0,	0,	0,	0,	0,	0,	0,	0,
41 0,	0,	0,	0,	0,	0,	0,	0,
42 0,	0,	0,	0,	0,	0,	0,	0,
43 0,	0,	0,	0,	0,	0,	0,	0,
44 0,	0,	0,	0,	0,	0,	0,	0,
45 0,	0,	0,	0,	0,	0,	0,	0,
46 0,	0,	0,	0,	0,	0,	0,	0,
47 0,	0,	0,	0,	0,	0,	0,	0,
48 0,	0,	0,	0,	0,	0,	0,	0,
49 0,	0,	0,	0,	0,	0,	0,	0,
50 0,	0,	0,	0,	0,	0,	0,	0,
51 0,	0,	0,	0,	0,	0,	0,	0,
52 0,	0,	0,	0,	0,	0,	0,	0,
53 0,	0,	0,	0,	0,	0,	0,	0,
54 0,	0,	0,	0,	0,	0,	0,	0,
55 };
56 
57 static int tflag[] = {	/* trailing flags */
58 0,	0,	0,	0,	0,	0,	0,	0,	/* ^@ ^A ^B ^C ^D ^E ^F ^G */
59 0,	0,	0,	0,	0,	0,	0,	0,	/* ^H ^I ^J ^K ^L ^M ^N ^O */
60 0,	0,	0,	0,	0,	0,	0,	0,	/* ^P ^Q ^R ^S ^T ^U ^V ^W */
61 0,	0,	0,	0,	0,	0,	0,	0,	/* ^X ^Y ^Z ^[ ^\ ^] ^^ ^_ */
62 0,	0,	0,	0,	0,	0,	0,	0,	/* sp  !  "  #  $  %  &  ' */
63 0,	0,	0,	0,	0,	0,	0,	0,	/*  (  )  *  +  ,  -  .  / */
64 0,	0,	0,	0,	0,	0,	0,	0,	/*  0  1  2  3  4  5  6  7 */
65 0,	0,	0,	0,	0,	0,	0,	0,	/*  8  9  :  ;  <  =  >  ? */
66 0,	0,	0,	0,	0,	0,	0,	0,	/*  @  A  B  C  D  E  F  G */
67 0,	0,	0,	0,	LDBL,	0,	0,	0,	/*  H  I  J  K  L  M  N  O */
68 0,	0,	0,	0,	0,	0,	0,	0,	/*  P  Q  R  S  T  U  V  W */
69 0,	0,	0,	0,	0,	0,	0,	0,	/*  X  Y  Z  [  \  ]  ^  _ */
70 0,	0,	0,	0,	0,	0,	0,	0,	/*  `  a  b  c  d  e  f  g */
71 SHORT,	0,	0,	0,	LONG,	0,	0,	0,	/*  h  i  j  k  l  m  n  o */
72 0,	0,	0,	0,	0,	0,	0,	0,	/*  p  q  r  s  t  u  v  w */
73 0,	0,	0,	0,	0,	0,	0,	0,	/*  x  y  z  {  |  }  ~ ^? */
74 
75 0,	0,	0,	0,	0,	0,	0,	0,
76 0,	0,	0,	0,	0,	0,	0,	0,
77 0,	0,	0,	0,	0,	0,	0,	0,
78 0,	0,	0,	0,	0,	0,	0,	0,
79 0,	0,	0,	0,	0,	0,	0,	0,
80 0,	0,	0,	0,	0,	0,	0,	0,
81 0,	0,	0,	0,	0,	0,	0,	0,
82 0,	0,	0,	0,	0,	0,	0,	0,
83 0,	0,	0,	0,	0,	0,	0,	0,
84 0,	0,	0,	0,	0,	0,	0,	0,
85 0,	0,	0,	0,	0,	0,	0,	0,
86 0,	0,	0,	0,	0,	0,	0,	0,
87 0,	0,	0,	0,	0,	0,	0,	0,
88 0,	0,	0,	0,	0,	0,	0,	0,
89 0,	0,	0,	0,	0,	0,	0,	0,
90 0,	0,	0,	0,	0,	0,	0,	0,
91 };
92 
93 static int ocvt_E(FILE *, va_list *, int, int, int);
94 static int ocvt_G(FILE *, va_list *, int, int, int);
95 static int ocvt_X(FILE *, va_list *, int, int, int);
96 static int ocvt_c(FILE *, va_list *, int, int, int);
97 static int ocvt_d(FILE *, va_list *, int, int, int);
98 static int ocvt_e(FILE *, va_list *, int, int, int);
99 static int ocvt_f(FILE *, va_list *, int, int, int);
100 static int ocvt_g(FILE *, va_list *, int, int, int);
101 static int ocvt_n(FILE *, va_list *, int, int, int);
102 static int ocvt_o(FILE *, va_list *, int, int, int);
103 static int ocvt_p(FILE *, va_list *, int, int, int);
104 static int ocvt_s(FILE *, va_list *, int, int, int);
105 static int ocvt_u(FILE *, va_list *, int, int, int);
106 static int ocvt_x(FILE *, va_list *, int, int, int);
107 
108 static int(*ocvt[])(FILE *, va_list *, int, int, int) = {
109 0,	0,	0,	0,	0,	0,	0,	0,	/* ^@ ^A ^B ^C ^D ^E ^F ^G */
110 0,	0,	0,	0,	0,	0,	0,	0,	/* ^H ^I ^J ^K ^L ^M ^N ^O */
111 0,	0,	0,	0,	0,	0,	0,	0,	/* ^P ^Q ^R ^S ^T ^U ^V ^W */
112 0,	0,	0,	0,	0,	0,	0,	0,	/* ^X ^Y ^Z ^[ ^\ ^] ^^ ^_ */
113 0,	0,	0,	0,	0,	0,	0,	0,	/* sp  !  "  #  $  %  &  ' */
114 0,	0,	0,	0,	0,	0,	0,	0,	/*  (  )  *  +  ,  -  .  / */
115 0,	0,	0,	0,	0,	0,	0,	0,	/*  0  1  2  3  4  5  6  7 */
116 0,	0,	0,	0,	0,	0,	0,	0,	/*  8  9  :  ;  <  =  >  ? */
117 0,	0,	0,	0,	0,	ocvt_E,	0,	ocvt_G,	/*  @  A  B  C  D  E  F  G */
118 0,	0,	0,	0,	0,	0,	0,	0,	/*  H  I  J  K  L  M  N  O */
119 0,	0,	0,	0,	0,	0,	0,	0,	/*  P  Q  R  S  T  U  V  W */
120 ocvt_X,	0,	0,	0,	0,	0,	0,	0,	/*  X  Y  Z  [  \  ]  ^  _ */
121 0,	0,	0,	ocvt_c,	ocvt_d,	ocvt_e,	ocvt_f,	ocvt_g,	/*  `  a  b  c  d  e  f  g */
122 0,	ocvt_d,	0,	0,	0,	0,	ocvt_n,	ocvt_o,	/*  h  i  j  k  l  m  n  o */
123 ocvt_p,	0,	0,	ocvt_s,	0,	ocvt_u,	0,	0,	/*  p  q  r  s  t  u  v  w */
124 ocvt_x,	0,	0,	0,	0,	0,	0,	0,	/*  x  y  z  {  |  }  ~ ^? */
125 
126 0,	0,	0,	0,	0,	0,	0,	0,
127 0,	0,	0,	0,	0,	0,	0,	0,
128 0,	0,	0,	0,	0,	0,	0,	0,
129 0,	0,	0,	0,	0,	0,	0,	0,
130 0,	0,	0,	0,	0,	0,	0,	0,
131 0,	0,	0,	0,	0,	0,	0,	0,
132 0,	0,	0,	0,	0,	0,	0,	0,
133 0,	0,	0,	0,	0,	0,	0,	0,
134 0,	0,	0,	0,	0,	0,	0,	0,
135 0,	0,	0,	0,	0,	0,	0,	0,
136 0,	0,	0,	0,	0,	0,	0,	0,
137 0,	0,	0,	0,	0,	0,	0,	0,
138 0,	0,	0,	0,	0,	0,	0,	0,
139 0,	0,	0,	0,	0,	0,	0,	0,
140 0,	0,	0,	0,	0,	0,	0,	0,
141 0,	0,	0,	0,	0,	0,	0,	0,
142 };
143 
144 static int nprint;
145 
146 int
147 vfprintf(FILE *f, const char *s, va_list args)
148 {
149 	int flags, width, precision;
150 
151 	nprint = 0;
152 	while(*s){
153 		if(*s != '%'){
154 			putc(*s++, f);
155 			nprint++;
156 			continue;
157 		}
158 		s++;
159 		flags = 0;
160 		while(lflag[*s&_IO_CHMASK]) flags |= lflag[*s++&_IO_CHMASK];
161 		if(*s == '*'){
162 			width = va_arg(args, int);
163 			s++;
164 			if(width<0){
165 				flags |= LEFT;
166 				width = -width;
167 			}
168 		}
169 		else{
170 			width = 0;
171 			while('0'<=*s && *s<='9') width = width*10 + *s++ - '0';
172 		}
173 		if(*s == '.'){
174 			s++;
175 			if(*s == '*'){
176 				precision = va_arg(args, int);
177 				s++;
178 			}
179 			else{
180 				precision = 0;
181 				while('0'<=*s && *s<='9') precision = precision*10 + *s++ - '0';
182 			}
183 		}
184 		else
185 			precision = -1;
186 		while(tflag[*s&_IO_CHMASK]) flags |= tflag[*s++&_IO_CHMASK];
187 		if(ocvt[*s]) nprint += (*ocvt[*s++])(f, &args, flags, width, precision);
188 		else if(*s){
189 			putc(*s++, f);
190 			nprint++;
191 		}
192 	}
193 	return ferror(f)? -1: nprint;;
194 }
195 
196 static int
197 ocvt_c(FILE *f, va_list *args, int flags, int width, int precision)
198 {
199 #pragma ref precision
200 	int i;
201 
202 	if(!(flags&LEFT)) for(i=1; i<width; i++) putc(' ', f);
203 	putc((unsigned char)va_arg(*args, int), f);
204 	if(flags&LEFT) for(i=1; i<width; i++) putc(' ', f);
205 	return width<1 ? 1 : width;
206 }
207 
208 static int
209 ocvt_s(FILE *f, va_list *args, int flags, int width, int precision)
210 {
211 	int i, n = 0;
212 	char *s;
213 
214 	s = va_arg(*args, char *);
215 	if(!(flags&LEFT)){
216 		if(precision >= 0)
217 			for(i=0; i!=precision && s[i]; i++);
218 		else
219 			for(i=0; s[i]; i++);
220 		for(; i<width; i++){
221 			putc(' ', f);
222 			n++;
223 		}
224 	}
225 	if(precision >= 0){
226 		for(i=0; i!=precision && *s; i++){
227 			putc(*s++, f);
228 			n++;
229 		}
230 	} else{
231 		for(i=0;*s;i++){
232 			putc(*s++, f);
233 			n++;
234 		}
235 	}
236 	if(flags&LEFT){
237 		for(; i<width; i++){
238 			putc(' ', f);
239 			n++;
240 		}
241 	}
242 	return n;
243 }
244 
245 static int
246 ocvt_n(FILE *f, va_list *args, int flags, int width, int precision)
247 {
248 #pragma ref f
249 #pragma ref width
250 #pragma ref precision
251 	if(flags&SHORT)
252 		*va_arg(*args, short *) = nprint;
253 	else if(flags&LONG)
254 		*va_arg(*args, long *) = nprint;
255 	else
256 		*va_arg(*args, int *) = nprint;
257 	return 0;
258 }
259 
260 /*
261  * Generic fixed-point conversion
262  *	f is the output FILE *;
263  *	args is the va_list * from which to get the number;
264  *	flags, width and precision are the results of printf-cracking;
265  *	radix is the number base to print in;
266  *	alphabet is the set of digits to use;
267  *	prefix is the prefix to print before non-zero numbers when
268  *	using ``alternate form.''
269  */
270 static int
271 ocvt_fixed(FILE *f, va_list *args, int flags, int width, int precision,
272 	int radix, int sgned, char alphabet[], char *prefix)
273 {
274 	char digits[128];	/* no reasonable machine will ever overflow this */
275 	char *sign;
276 	char *dp;
277 	long snum;
278 	unsigned long num;
279 	int nout, npad, nlzero;
280 
281 	if(sgned){
282 		if(flags&PTR) snum = (long)va_arg(*args, void *);
283 		else if(flags&SHORT) snum = va_arg(*args, short);
284 		else if(flags&LONG) snum = va_arg(*args, long);
285 		else snum = va_arg(*args, int);
286 		if(snum < 0){
287 			sign = "-";
288 			num = -snum;
289 		} else{
290 			if(flags&SIGN) sign = "+";
291 			else if(flags&SPACE) sign = " ";
292 			else sign = "";
293 			num = snum;
294 		}
295 	} else {
296 		sign = "";
297 		if(flags&PTR) num = (long)va_arg(*args, void *);
298 		else if(flags&SHORT) num = va_arg(*args, unsigned short);
299 		else if(flags&LONG) num = va_arg(*args, unsigned long);
300 		else num = va_arg(*args, unsigned int);
301 	}
302 	if(num == 0) prefix = "";
303 	dp = digits;
304 	do{
305 		*dp++ = alphabet[num%radix];
306 		num /= radix;
307 	}while(num);
308 	if(precision==0 && dp-digits==1 && dp[-1]=='0')
309 		dp--;
310 	nlzero = precision-(dp-digits);
311 	if(nlzero < 0) nlzero = 0;
312 	if(flags&ALT){
313 		if(radix == 8) if(dp[-1]=='0' || nlzero) prefix = "";
314 	}
315 	else prefix = "";
316 	nout = dp-digits+nlzero+strlen(prefix)+strlen(sign);
317 	npad = width-nout;
318 	if(npad < 0) npad = 0;
319 	nout += npad;
320 	if(!(flags&LEFT)){
321 		if(flags&ZPAD && precision <= 0){
322 			fputs(sign, f);
323 			fputs(prefix, f);
324 			while(npad){
325 				putc('0', f);
326 				--npad;
327 			}
328 		} else{
329 			while(npad){
330 				putc(' ', f);
331 				--npad;
332 			}
333 			fputs(sign, f);
334 			fputs(prefix, f);
335 		}
336 		while(nlzero){
337 			putc('0', f);
338 			--nlzero;
339 		}
340 		while(dp!=digits) putc(*--dp, f);
341 	}
342 	else{
343 		fputs(sign, f);
344 		fputs(prefix, f);
345 		while(nlzero){
346 			putc('0', f);
347 			--nlzero;
348 		}
349 		while(dp != digits) putc(*--dp, f);
350 		while(npad){
351 			putc(' ', f);
352 			--npad;
353 		}
354 	}
355 	return nout;
356 }
357 
358 static int
359 ocvt_X(FILE *f, va_list *args, int flags, int width, int precision)
360 {
361 	return ocvt_fixed(f, args, flags, width, precision, 16, 0, "0123456789ABCDEF", "0X");
362 }
363 
364 static int
365 ocvt_d(FILE *f, va_list *args, int flags, int width, int precision)
366 {
367 	return ocvt_fixed(f, args, flags, width, precision, 10, 1, "0123456789", "");
368 }
369 
370 static int
371 ocvt_o(FILE *f, va_list *args, int flags, int width, int precision)
372 {
373 	return ocvt_fixed(f, args, flags, width, precision, 8, 0, "01234567", "0");
374 }
375 
376 static int
377 ocvt_p(FILE *f, va_list *args, int flags, int width, int precision)
378 {
379 	return ocvt_fixed(f, args, flags|PTR|ALT, width, precision, 16, 0,
380 		"0123456789ABCDEF", "0X");
381 }
382 
383 static int
384 ocvt_u(FILE *f, va_list *args, int flags, int width, int precision)
385 {
386 	return ocvt_fixed(f, args, flags, width, precision, 10, 0, "0123456789", "");
387 }
388 
389 static int
390 ocvt_x(FILE *f, va_list *args, int flags, int width, int precision)
391 {
392 	return ocvt_fixed(f, args, flags, width, precision, 16, 0, "0123456789abcdef", "0x");
393 }
394 
395 static int ocvt_flt(FILE *, va_list *, int, int, int, char);
396 
397 static int
398 ocvt_E(FILE *f, va_list *args, int flags, int width, int precision)
399 {
400 	return ocvt_flt(f, args, flags, width, precision, 'E');
401 }
402 
403 static int
404 ocvt_G(FILE *f, va_list *args, int flags, int width, int precision)
405 {
406 	return ocvt_flt(f, args, flags, width, precision, 'G');
407 }
408 
409 static int
410 ocvt_e(FILE *f, va_list *args, int flags, int width, int precision)
411 {
412 	return ocvt_flt(f, args, flags, width, precision, 'e');
413 }
414 
415 static int
416 ocvt_f(FILE *f, va_list *args, int flags, int width, int precision)
417 {
418 	return ocvt_flt(f, args, flags, width, precision, 'f');
419 }
420 
421 static int
422 ocvt_g(FILE *f, va_list *args, int flags, int width, int precision)
423 {
424 	return ocvt_flt(f, args, flags, width, precision, 'g');
425 }
426 
427 static int
428 ocvt_flt(FILE *f, va_list *args, int flags, int width, int precision, char afmt)
429 {
430 	extern char *_dtoa(double, int, int, int*, int*, char **);
431 	int echr;
432 	char *digits, *edigits;
433 	int exponent;
434 	char fmt;
435 	int sign;
436 	int ndig;
437 	int nout, i;
438 	char ebuf[20];	/* no sensible machine will overflow this */
439 	char *eptr;
440 	double d;
441 
442 	echr = 'e';
443 	fmt = afmt;
444 	d = va_arg(*args, double);
445 	if(precision < 0) precision = 6;
446 	switch(fmt){
447 	case 'f':
448 		digits = _dtoa(d, 3, precision, &exponent, &sign, &edigits);
449 		break;
450 	case 'E':
451 		echr = 'E';
452 		fmt = 'e';
453 		/* fall through */
454 	case 'e':
455 		digits = _dtoa(d, 2, 1+precision, &exponent, &sign, &edigits);
456 		break;
457 	case 'G':
458 		echr = 'E';
459 		/* fall through */
460 	case 'g':
461 		if (precision > 0)
462 			digits = _dtoa(d, 2, precision, &exponent, &sign, &edigits);
463 		else {
464 			digits = _dtoa(d, 0, precision, &exponent, &sign, &edigits);
465 			precision = edigits - digits;
466 			if (exponent > precision && exponent <= precision + 4)
467 				precision = exponent;
468 			}
469 		if(exponent >= -3 && exponent <= precision){
470 			fmt = 'f';
471 			precision -= exponent;
472 		}else{
473 			fmt = 'e';
474 			--precision;
475 		}
476 		break;
477 	}
478 	if (exponent == 9999) {
479 		/* Infinity or Nan */
480 		precision = 0;
481 		exponent = edigits - digits;
482 		fmt = 'f';
483 	}
484 	ndig = edigits-digits;
485 	if((afmt=='g' || afmt=='G') && !(flags&ALT)){	/* knock off trailing zeros */
486 		if(fmt == 'f'){
487 			if(precision+exponent > ndig) {
488 				precision = ndig - exponent;
489 				if(precision < 0)
490 					precision = 0;
491 			}
492 		}
493 		else{
494 			if(precision > ndig-1) precision = ndig-1;
495 		}
496 	}
497 	nout = precision;				/* digits after decimal point */
498 	if(precision!=0 || flags&ALT) nout++;		/* decimal point */
499 	if(fmt=='f' && exponent>0) nout += exponent;	/* digits before decimal point */
500 	else nout++;					/* there's always at least one */
501 	if(sign || flags&(SPACE|SIGN)) nout++;		/* sign */
502 	if(fmt != 'f'){					/* exponent */
503 		eptr = ebuf;
504 		for(i=exponent<=0?1-exponent:exponent-1; i; i/=10)
505 			*eptr++ = '0' + i%10;
506 		while(eptr<ebuf+2) *eptr++ = '0';
507 		nout += eptr-ebuf+2;			/* e+99 */
508 	}
509 	if(!(flags&ZPAD) && !(flags&LEFT))
510 		while(nout < width){
511 			putc(' ', f);
512 			nout++;
513 		}
514 	if(sign) putc('-', f);
515 	else if(flags&SIGN) putc('+', f);
516 	else if(flags&SPACE) putc(' ', f);
517 	if(flags&ZPAD)
518 		while(nout < width){
519 			putc('0', f);
520 			nout++;
521 		}
522 	if(fmt == 'f'){
523 		for(i=0; i<exponent; i++) putc(i<ndig?digits[i]:'0', f);
524 		if(i == 0) putc('0', f);
525 		if(precision>0 || flags&ALT) putc('.', f);
526 		for(i=0; i!=precision; i++)
527 			putc(0<=i+exponent && i+exponent<ndig?digits[i+exponent]:'0', f);
528 	}
529 	else{
530 		putc(digits[0], f);
531 		if(precision>0 || flags&ALT) putc('.', f);
532 		for(i=0; i!=precision; i++) putc(i<ndig-1?digits[i+1]:'0', f);
533 	}
534 	if(fmt != 'f'){
535 		putc(echr, f);
536 		putc(exponent<=0?'-':'+', f);
537 		while(eptr>ebuf) putc(*--eptr, f);
538 	}
539 	while(nout < width){
540 		putc(' ', f);
541 		nout++;
542 	}
543 	return nout;
544 }
545