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