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