xref: /plan9-contrib/sys/src/libstdio/vfprintf.c (revision 59cc4ca53493a3c6d2349fe2b7f7c40f7dce7294)
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 	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((afmt=='g' || afmt=='G') && !(flags&ALT)){	/* knock off trailing zeros */
492 		if(fmt == 'f'){
493 			if(precision+exponent > ndig) {
494 				precision = ndig - exponent;
495 				if(precision < 0)
496 					precision = 0;
497 			}
498 		}
499 		else{
500 			if(precision > ndig-1) precision = ndig-1;
501 		}
502 	}
503 	nout = precision;				/* digits after decimal point */
504 	if(precision!=0 || flags&ALT) nout++;		/* decimal point */
505 	if(fmt=='f' && exponent>0) nout += exponent;	/* digits before decimal point */
506 	else nout++;					/* there's always at least one */
507 	if(sign || flags&(SPACE|SIGN)) nout++;		/* sign */
508 	if(fmt != 'f'){					/* exponent */
509 		eptr = ebuf;
510 		for(i=exponent<=0?1-exponent:exponent-1; i; i/=10)
511 			*eptr++ = '0' + i%10;
512 		while(eptr<ebuf+2) *eptr++ = '0';
513 		nout += eptr-ebuf+2;			/* e+99 */
514 	}
515 	if(!(flags&ZPAD) && !(flags&LEFT))
516 		while(nout < width){
517 			putc(' ', f);
518 			nout++;
519 		}
520 	if(sign) putc('-', f);
521 	else if(flags&SIGN) putc('+', f);
522 	else if(flags&SPACE) putc(' ', f);
523 	if(flags&ZPAD)
524 		while(nout < width){
525 			putc('0', f);
526 			nout++;
527 		}
528 	if(fmt == 'f'){
529 		for(i=0; i<exponent; i++) putc(i<ndig?digits[i]:'0', f);
530 		if(i == 0) putc('0', f);
531 		if(precision>0 || flags&ALT) putc('.', f);
532 		for(i=0; i!=precision; i++)
533 			putc(0<=i+exponent && i+exponent<ndig?digits[i+exponent]:'0', f);
534 	}
535 	else{
536 		putc(digits[0], f);
537 		if(precision>0 || flags&ALT) putc('.', f);
538 		for(i=0; i!=precision; i++) putc(i<ndig-1?digits[i+1]:'0', f);
539 	}
540 	if(fmt != 'f'){
541 		putc(echr, f);
542 		putc(exponent<=0?'-':'+', f);
543 		while(eptr>ebuf) putc(*--eptr, f);
544 	}
545 	while(nout < width){
546 		putc(' ', f);
547 		nout++;
548 	}
549 	return nout;
550 }
551