xref: /plan9-contrib/sys/src/ape/lib/ap/stdio/vfprintf.c (revision 3e12c5d1bb89fc02707907988834ef147769ddaf)
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 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(!(flags&LEFT)){
220 		if(precision >= 0)
221 			for(i=0; i!=precision && s[i]; i++);
222 		else
223 			for(i=0; s[i]; i++);
224 		for(; i<width; i++){
225 			putc(' ', f);
226 			n++;
227 		}
228 	}
229 	if(precision >= 0){
230 		for(i=0; i!=precision && *s; i++){
231 			putc(*s++, f);
232 			n++;
233 		}
234 	} else{
235 		for(i=0;*s;i++){
236 			putc(*s++, f);
237 			n++;
238 		}
239 	}
240 	if(flags&LEFT){
241 		for(; i<width; i++){
242 			putc(' ', f);
243 			n++;
244 		}
245 	}
246 	return n;
247 }
248 
249 static int
250 ocvt_n(FILE *f, va_list *args, int flags, int width, int precision)
251 {
252 #pragma ref f
253 #pragma ref width
254 #pragma ref precision
255 	if(flags&SHORT)
256 		*va_arg(*args, short *) = nprint;
257 	else if(flags&LONG)
258 		*va_arg(*args, long *) = nprint;
259 	else
260 		*va_arg(*args, int *) = nprint;
261 	return 0;
262 }
263 
264 /*
265  * Generic fixed-point conversion
266  *	f is the output FILE *;
267  *	args is the va_list * from which to get the number;
268  *	flags, width and precision are the results of printf-cracking;
269  *	radix is the number base to print in;
270  *	alphabet is the set of digits to use;
271  *	prefix is the prefix to print before non-zero numbers when
272  *	using ``alternate form.''
273  */
274 static int
275 ocvt_fixed(FILE *f, va_list *args, int flags, int width, int precision,
276 	int radix, int sgned, char alphabet[], char *prefix)
277 {
278 	char digits[128];	/* no reasonable machine will ever overflow this */
279 	char *sign;
280 	char *dp;
281 	long snum;
282 	unsigned long num;
283 	int nout, npad, nlzero;
284 
285 	if(sgned){
286 		if(flags&PTR) snum = (long)va_arg(*args, void *);
287 		else if(flags&SHORT) snum = va_arg(*args, short);
288 		else if(flags&LONG) snum = va_arg(*args, long);
289 		else snum = va_arg(*args, int);
290 		if(snum < 0){
291 			sign = "-";
292 			num = -snum;
293 		} else{
294 			if(flags&SIGN) sign = "+";
295 			else if(flags&SPACE) sign = " ";
296 			else sign = "";
297 			num = snum;
298 		}
299 	} else {
300 		sign = "";
301 		if(flags&PTR) num = (long)va_arg(*args, void *);
302 		else if(flags&SHORT) num = va_arg(*args, unsigned short);
303 		else if(flags&LONG) num = va_arg(*args, unsigned long);
304 		else num = va_arg(*args, unsigned int);
305 	}
306 	if(num == 0) prefix = "";
307 	dp = digits;
308 	do{
309 		*dp++ = alphabet[num%radix];
310 		num /= radix;
311 	}while(num);
312 	if(precision==0 && dp-digits==1 && dp[-1]=='0')
313 		dp--;
314 	nlzero = precision-(dp-digits);
315 	if(nlzero < 0) nlzero = 0;
316 	if(flags&ALT){
317 		if(radix == 8) if(dp[-1]=='0' || nlzero) prefix = "";
318 	}
319 	else prefix = "";
320 	nout = dp-digits+nlzero+strlen(prefix)+strlen(sign);
321 	npad = width-nout;
322 	if(npad < 0) npad = 0;
323 	nout += npad;
324 	if(!(flags&LEFT)){
325 		if(flags&ZPAD && precision < 0){
326 			fputs(sign, f);
327 			fputs(prefix, f);
328 			while(npad){
329 				putc('0', f);
330 				--npad;
331 			}
332 		} else{
333 			while(npad){
334 				putc(' ', f);
335 				--npad;
336 			}
337 			fputs(sign, f);
338 			fputs(prefix, f);
339 		}
340 		while(nlzero){
341 			putc('0', f);
342 			--nlzero;
343 		}
344 		while(dp!=digits) putc(*--dp, f);
345 	}
346 	else{
347 		fputs(sign, f);
348 		fputs(prefix, f);
349 		while(nlzero){
350 			putc('0', f);
351 			--nlzero;
352 		}
353 		while(dp != digits) putc(*--dp, f);
354 		while(npad){
355 			putc(' ', f);
356 			--npad;
357 		}
358 	}
359 	return nout;
360 }
361 
362 static int
363 ocvt_X(FILE *f, va_list *args, int flags, int width, int precision)
364 {
365 	return ocvt_fixed(f, args, flags, width, precision, 16, 0, "0123456789ABCDEF", "0X");
366 }
367 
368 static int
369 ocvt_d(FILE *f, va_list *args, int flags, int width, int precision)
370 {
371 	return ocvt_fixed(f, args, flags, width, precision, 10, 1, "0123456789", "");
372 }
373 
374 static int
375 ocvt_o(FILE *f, va_list *args, int flags, int width, int precision)
376 {
377 	return ocvt_fixed(f, args, flags, width, precision, 8, 0, "01234567", "0");
378 }
379 
380 static int
381 ocvt_p(FILE *f, va_list *args, int flags, int width, int precision)
382 {
383 	return ocvt_fixed(f, args, flags|PTR|ALT, width, precision, 16, 0,
384 		"0123456789ABCDEF", "0X");
385 }
386 
387 static int
388 ocvt_u(FILE *f, va_list *args, int flags, int width, int precision)
389 {
390 	return ocvt_fixed(f, args, flags, width, precision, 10, 0, "0123456789", "");
391 }
392 
393 static int
394 ocvt_x(FILE *f, va_list *args, int flags, int width, int precision)
395 {
396 	return ocvt_fixed(f, args, flags, width, precision, 16, 0, "0123456789abcdef", "0x");
397 }
398 
399 static int ocvt_flt(FILE *, va_list *, int, int, int, char);
400 
401 static int
402 ocvt_E(FILE *f, va_list *args, int flags, int width, int precision)
403 {
404 	return ocvt_flt(f, args, flags, width, precision, 'E');
405 }
406 
407 static int
408 ocvt_G(FILE *f, va_list *args, int flags, int width, int precision)
409 {
410 	return ocvt_flt(f, args, flags, width, precision, 'G');
411 }
412 
413 static int
414 ocvt_e(FILE *f, va_list *args, int flags, int width, int precision)
415 {
416 	return ocvt_flt(f, args, flags, width, precision, 'e');
417 }
418 
419 static int
420 ocvt_f(FILE *f, va_list *args, int flags, int width, int precision)
421 {
422 	return ocvt_flt(f, args, flags, width, precision, 'f');
423 }
424 
425 static int
426 ocvt_g(FILE *f, va_list *args, int flags, int width, int precision)
427 {
428 	return ocvt_flt(f, args, flags, width, precision, 'g');
429 }
430 
431 static int
432 ocvt_flt(FILE *f, va_list *args, int flags, int width, int precision, char afmt)
433 {
434 	extern char *_dtoa(double, int, int, int*, int*, char **);
435 	int echr;
436 	char *digits, *edigits;
437 	int exponent;
438 	char fmt;
439 	int sign;
440 	int ndig;
441 	int nout, i;
442 	char ebuf[20];	/* no sensible machine will overflow this */
443 	char *eptr;
444 	double d;
445 
446 	echr = 'e';
447 	fmt = afmt;
448 	d = va_arg(*args, double);
449 	if(precision < 0) precision = 6;
450 	switch(fmt){
451 	case 'f':
452 		digits = _dtoa(d, 3, precision, &exponent, &sign, &edigits);
453 		break;
454 	case 'E':
455 		echr = 'E';
456 		fmt = 'e';
457 		/* fall through */
458 	case 'e':
459 		digits = _dtoa(d, 2, 1+precision, &exponent, &sign, &edigits);
460 		break;
461 	case 'G':
462 		echr = 'E';
463 		/* fall through */
464 	case 'g':
465 		if (precision > 0)
466 			digits = _dtoa(d, 2, precision, &exponent, &sign, &edigits);
467 		else {
468 			digits = _dtoa(d, 0, precision, &exponent, &sign, &edigits);
469 			precision = edigits - digits;
470 			if (exponent > precision && exponent <= precision + 4)
471 				precision = exponent;
472 			}
473 		if(exponent >= -3 && exponent <= precision){
474 			fmt = 'f';
475 			precision -= exponent;
476 		}else{
477 			fmt = 'e';
478 			--precision;
479 		}
480 		break;
481 	}
482 	if (exponent == 9999) {
483 		/* Infinity or Nan */
484 		precision = 0;
485 		exponent = edigits - digits;
486 		fmt = 'f';
487 	}
488 	ndig = edigits-digits;
489 	if(ndig == 0) {
490 		ndig = 1;
491 		digits = "0";
492 	}
493 	if((afmt=='g' || afmt=='G') && !(flags&ALT)){	/* knock off trailing zeros */
494 		if(fmt == 'f'){
495 			if(precision+exponent > ndig) {
496 				precision = ndig - exponent;
497 				if(precision < 0)
498 					precision = 0;
499 			}
500 		}
501 		else{
502 			if(precision > ndig-1) precision = ndig-1;
503 		}
504 	}
505 	nout = precision;				/* digits after decimal point */
506 	if(precision!=0 || flags&ALT) nout++;		/* decimal point */
507 	if(fmt=='f' && exponent>0) nout += exponent;	/* digits before decimal point */
508 	else nout++;					/* there's always at least one */
509 	if(sign || flags&(SPACE|SIGN)) nout++;		/* sign */
510 	if(fmt != 'f'){					/* exponent */
511 		eptr = ebuf;
512 		for(i=exponent<=0?1-exponent:exponent-1; i; i/=10)
513 			*eptr++ = '0' + i%10;
514 		while(eptr<ebuf+2) *eptr++ = '0';
515 		nout += eptr-ebuf+2;			/* e+99 */
516 	}
517 	if(!(flags&LEFT))
518 		while(nout < width){
519 			putc(' ', f);
520 			nout++;
521 		}
522 	if(sign) putc('-', f);
523 	else if(flags&SIGN) putc('+', f);
524 	else if(flags&SPACE) putc(' ', f);
525 	if(fmt == 'f'){
526 		for(i=0; i<exponent; i++) putc(i<ndig?digits[i]:'0', f);
527 		if(i == 0) putc('0', f);
528 		if(precision>0 || flags&ALT) putc('.', f);
529 		for(i=0; i!=precision; i++)
530 			putc(0<=i+exponent && i+exponent<ndig?digits[i+exponent]:'0', f);
531 	}
532 	else{
533 		putc(digits[0], f);
534 		if(precision>0 || flags&ALT) putc('.', f);
535 		for(i=0; i!=precision; i++) putc(i<ndig-1?digits[i+1]:'0', f);
536 	}
537 	if(fmt != 'f'){
538 		putc(echr, f);
539 		putc(exponent<=0?'-':'+', f);
540 		while(eptr>ebuf) putc(*--eptr, f);
541 	}
542 	while(nout < width){
543 		putc(' ', f);
544 		nout++;
545 	}
546 	return nout;
547 }
548