xref: /csrg-svn/usr.bin/ex/printf.c (revision 48255)
1*48255Sbostic /*-
2*48255Sbostic  * Copyright (c) 1980 The Regents of the University of California.
3*48255Sbostic  * All rights reserved.
4*48255Sbostic  *
5*48255Sbostic  * %sccs.include.proprietary.c%
621708Sdist  */
721708Sdist 
821708Sdist #ifndef lint
9*48255Sbostic static char sccsid[] = "@(#)printf.c	7.5 (Berkeley) 04/17/91";
10*48255Sbostic #endif /* not lint */
11*48255Sbostic 
12*48255Sbostic #ifndef lint
1330596Sconrad /* The pwb version this is based on */
1430596Sconrad static char *printf_id = "@(#) printf.c:2.2 6/5/79";
15*48255Sbostic #endif /* not lint */
1621708Sdist 
1730596Sconrad #include <varargs.h>
1830596Sconrad 
19466Smark /*
20466Smark  * This version of printf is compatible with the Version 7 C
21466Smark  * printf. The differences are only minor except that this
22466Smark  * printf assumes it is to print through putchar. Version 7
23466Smark  * printf is more general (and is much larger) and includes
24466Smark  * provisions for floating point.
25466Smark  */
26466Smark 
27466Smark #define MAXOCT	11	/* Maximum octal digits in a long */
28466Smark #define MAXINT	32767	/* largest normal length positive integer */
29466Smark #define BIG	1000000000  /* largest power of 10 less than an unsigned long */
30466Smark #define MAXDIGS	10	/* number of digits in BIG */
31466Smark 
32466Smark static int width, sign, fill;
33466Smark 
34466Smark char *_p_dconv();
35466Smark 
3630596Sconrad /* VARARGS */
3730596Sconrad ex_printf(va_alist)
38466Smark 	va_dcl
39466Smark {
40466Smark 	va_list ap;
41466Smark 	register char *fmt;
42466Smark 	char fcode;
43466Smark 	int prec;
44466Smark 	int length,mask1,nbits,n;
45466Smark 	long int mask2, num;
46466Smark 	register char *bptr;
47466Smark 	char *ptr;
48466Smark 	char buf[134];
49466Smark 
50466Smark 	va_start(ap);
51466Smark 	fmt = va_arg(ap,char *);
52466Smark 	for (;;) {
53466Smark 		/* process format string first */
54466Smark 		while ((fcode = *fmt++)!='%') {
55466Smark 			/* ordinary (non-%) character */
56466Smark 			if (fcode=='\0')
57466Smark 				return;
5830596Sconrad 			ex_putchar(fcode);
59466Smark 		}
60466Smark 		/* length modifier: -1 for h, 1 for l, 0 for none */
61466Smark 		length = 0;
62466Smark 		/* check for a leading - sign */
63466Smark 		sign = 0;
64466Smark 		if (*fmt == '-') {
65466Smark 			sign++;
66466Smark 			fmt++;
67466Smark 		}
68466Smark 		/* a '0' may follow the - sign */
69466Smark 		/* this is the requested fill character */
70466Smark 		fill = 1;
71466Smark 		if (*fmt == '0') {
72466Smark 			fill--;
73466Smark 			fmt++;
74466Smark 		}
75466Smark 
76466Smark 		/* Now comes a digit string which may be a '*' */
77466Smark 		if (*fmt == '*') {
78466Smark 			width = va_arg(ap, int);
79466Smark 			if (width < 0) {
80466Smark 				width = -width;
81466Smark 				sign = !sign;
82466Smark 			}
83466Smark 			fmt++;
84466Smark 		}
85466Smark 		else {
86466Smark 			width = 0;
87466Smark 			while (*fmt>='0' && *fmt<='9')
88466Smark 				width = width * 10 + (*fmt++ - '0');
89466Smark 		}
90466Smark 
91466Smark 		/* maybe a decimal point followed by more digits (or '*') */
92466Smark 		if (*fmt=='.') {
93466Smark 			if (*++fmt == '*') {
94466Smark 				prec = va_arg(ap, int);
95466Smark 				fmt++;
96466Smark 			}
97466Smark 			else {
98466Smark 				prec = 0;
99466Smark 				while (*fmt>='0' && *fmt<='9')
100466Smark 					prec = prec * 10 + (*fmt++ - '0');
101466Smark 			}
102466Smark 		}
103466Smark 		else
104466Smark 			prec = -1;
105466Smark 
106466Smark 		/*
107466Smark 		 * At this point, "sign" is nonzero if there was
108466Smark 		 * a sign, "fill" is 0 if there was a leading
109466Smark 		 * zero and 1 otherwise, "width" and "prec"
110466Smark 		 * contain numbers corresponding to the digit
111466Smark 		 * strings before and after the decimal point,
112466Smark 		 * respectively, and "fmt" addresses the next
113466Smark 		 * character after the whole mess. If there was
114466Smark 		 * no decimal point, "prec" will be -1.
115466Smark 		 */
116466Smark 		switch (*fmt) {
117466Smark 			case 'L':
118466Smark 			case 'l':
119466Smark 				length = 2;
120466Smark 				/* no break!! */
121466Smark 			case 'h':
122466Smark 			case 'H':
123466Smark 				length--;
124466Smark 				fmt++;
125466Smark 				break;
126466Smark 		}
127466Smark 
128466Smark 		/*
129466Smark 		 * At exit from the following switch, we will
130466Smark 		 * emit the characters starting at "bptr" and
131466Smark 		 * ending at "ptr"-1, unless fcode is '\0'.
132466Smark 		 */
133466Smark 		switch (fcode = *fmt++) {
134466Smark 			/* process characters and strings first */
135466Smark 			case 'c':
136466Smark 				buf[0] = va_arg(ap, int);
137466Smark 				ptr = bptr = &buf[0];
138466Smark 				if (buf[0] != '\0')
139466Smark 					ptr++;
140466Smark 				break;
141466Smark 			case 's':
142466Smark 				bptr = va_arg(ap,char *);
143466Smark 				if (bptr==0)
144466Smark 					bptr = "(null pointer)";
145466Smark 				if (prec < 0)
146466Smark 					prec = MAXINT;
147466Smark 				for (n=0; *bptr++ && n < prec; n++) ;
148466Smark 				ptr = --bptr;
149466Smark 				bptr -= n;
150466Smark 				break;
151466Smark 			case 'O':
152466Smark 				length = 1;
153466Smark 				fcode = 'o';
154466Smark 				/* no break */
155466Smark 			case 'o':
156466Smark 			case 'X':
157466Smark 			case 'x':
158466Smark 				if (length > 0)
159466Smark 					num = va_arg(ap,long);
160466Smark 				else
161466Smark 					num = (unsigned)va_arg(ap,int);
162466Smark 				if (fcode=='o') {
163466Smark 					mask1 = 0x7;
164466Smark 					mask2 = 0x1fffffffL;
165466Smark 					nbits = 3;
166466Smark 				}
167466Smark 				else {
168466Smark 					mask1 = 0xf;
169466Smark 					mask2 = 0x0fffffffL;
170466Smark 					nbits = 4;
171466Smark 				}
172466Smark 				n = (num!=0);
173466Smark 				bptr = buf + MAXOCT + 3;
174466Smark 				/* shift and mask for speed */
175466Smark 				do
176466Smark 				    if (((int) num & mask1) < 10)
177466Smark 					*--bptr = ((int) num & mask1) + 060;
178466Smark 				    else
179466Smark 					*--bptr = ((int) num & mask1) + 0127;
180466Smark 				while (num = (num >> nbits) & mask2);
181466Smark 
182466Smark 				if (fcode=='o') {
183466Smark 					if (n)
184466Smark 						*--bptr = '0';
185466Smark 				}
186466Smark 				else
187466Smark 					if (!sign && fill <= 0) {
18830596Sconrad 						ex_putchar('0');
18930596Sconrad 						ex_putchar(fcode);
190466Smark 						width -= 2;
191466Smark 					}
192466Smark 					else {
193466Smark 						*--bptr = fcode;
194466Smark 						*--bptr = '0';
195466Smark 					}
196466Smark 				ptr = buf + MAXOCT + 3;
197466Smark 				break;
198466Smark 			case 'D':
199466Smark 			case 'U':
200466Smark 			case 'I':
201466Smark 				length = 1;
202466Smark 				fcode = fcode + 'a' - 'A';
203466Smark 				/* no break */
204466Smark 			case 'd':
205466Smark 			case 'i':
206466Smark 			case 'u':
207466Smark 				if (length > 0)
208466Smark 					num = va_arg(ap,long);
209466Smark 				else {
210466Smark 					n = va_arg(ap,int);
211466Smark 					if (fcode=='u')
212466Smark 						num = (unsigned) n;
213466Smark 					else
214466Smark 						num = (long) n;
215466Smark 				}
216466Smark 				if (n = (fcode != 'u' && num < 0))
217466Smark 					num = -num;
218466Smark 				/* now convert to digits */
219466Smark 				bptr = _p_dconv(num, buf);
220466Smark 				if (n)
221466Smark 					*--bptr = '-';
222466Smark 				if (fill == 0)
223466Smark 					fill = -1;
224466Smark 				ptr = buf + MAXDIGS + 1;
225466Smark 				break;
226466Smark 			default:
227466Smark 				/* not a control character,
228466Smark 				 * print it.
229466Smark 				 */
230466Smark 				ptr = bptr = &fcode;
231466Smark 				ptr++;
232466Smark 				break;
233466Smark 			}
234466Smark 			if (fcode != '\0')
235466Smark 				_p_emit(bptr,ptr);
236466Smark 	}
237466Smark 	va_end(ap);
238466Smark }
239466Smark 
240466Smark /* _p_dconv converts the unsigned long integer "value" to
241466Smark  * printable decimal and places it in "buffer", right-justified.
242466Smark  * The value returned is the address of the first non-zero character,
243466Smark  * or the address of the last character if all are zero.
244466Smark  * The result is NOT null terminated, and is MAXDIGS characters long,
245466Smark  * starting at buffer[1] (to allow for insertion of a sign).
246466Smark  *
247466Smark  * This program assumes it is running on 2's complement machine
248466Smark  * with reasonable overflow treatment.
249466Smark  */
250466Smark char *
251466Smark _p_dconv(value, buffer)
252466Smark 	long value;
253466Smark 	char *buffer;
254466Smark {
255466Smark 	register char *bp;
256466Smark 	register int svalue;
257466Smark 	int n;
258466Smark 	long lval;
259466Smark 
260466Smark 	bp = buffer;
261466Smark 
262466Smark 	/* zero is a special case */
263466Smark 	if (value == 0) {
264466Smark 		bp += MAXDIGS;
265466Smark 		*bp = '0';
266466Smark 		return(bp);
267466Smark 	}
268466Smark 
269466Smark 	/* develop the leading digit of the value in "n" */
270466Smark 	n = 0;
271466Smark 	while (value < 0) {
272466Smark 		value -= BIG;	/* will eventually underflow */
273466Smark 		n++;
274466Smark 	}
275466Smark 	while ((lval = value - BIG) >= 0) {
276466Smark 		value = lval;
277466Smark 		n++;
278466Smark 	}
279466Smark 
280466Smark 	/* stash it in buffer[1] to allow for a sign */
281466Smark 	bp[1] = n + '0';
282466Smark 	/*
283466Smark 	 * Now develop the rest of the digits. Since speed counts here,
284466Smark 	 * we do it in two loops. The first gets "value" down until it
285466Smark 	 * is no larger than MAXINT. The second one uses integer divides
286466Smark 	 * rather than long divides to speed it up.
287466Smark 	 */
288466Smark 	bp += MAXDIGS + 1;
289466Smark 	while (value > MAXINT) {
290466Smark 		*--bp = (int)(value % 10) + '0';
291466Smark 		value /= 10;
292466Smark 	}
293466Smark 
294466Smark 	/* cannot lose precision */
295466Smark 	svalue = value;
296466Smark 	while (svalue > 0) {
297466Smark 		*--bp = (svalue % 10) + '0';
298466Smark 		svalue /= 10;
299466Smark 	}
300466Smark 
301466Smark 	/* fill in intermediate zeroes if needed */
302466Smark 	if (buffer[1] != '0') {
303466Smark 		while (bp > buffer + 2)
304466Smark 			*--bp = '0';
305466Smark 		--bp;
306466Smark 	}
307466Smark 	return(bp);
308466Smark }
309466Smark 
310466Smark /*
311466Smark  * This program sends string "s" to putchar. The character after
312466Smark  * the end of "s" is given by "send". This allows the size of the
313466Smark  * field to be computed; it is stored in "alen". "width" contains the
314466Smark  * user specified length. If width<alen, the width will be taken to
315466Smark  * be alen. "sign" is zero if the string is to be right-justified
316466Smark  * in the field, nonzero if it is to be left-justified. "fill" is
317466Smark  * 0 if the string is to be padded with '0', positive if it is to be
318466Smark  * padded with ' ', and negative if an initial '-' should appear before
319466Smark  * any padding in right-justification (to avoid printing "-3" as
320466Smark  * "000-3" where "-0003" was intended).
321466Smark  */
322466Smark _p_emit(s, send)
323466Smark 	register char *s;
324466Smark 	char *send;
325466Smark {
326466Smark 	char cfill;
327466Smark 	register int alen;
328466Smark 	int npad;
329466Smark 
330466Smark 	alen = send - s;
331466Smark 	if (alen > width)
332466Smark 		width = alen;
333466Smark 	cfill = fill>0? ' ': '0';
334466Smark 
335466Smark 	/* we may want to print a leading '-' before anything */
336466Smark 	if (*s == '-' && fill < 0) {
33730596Sconrad 		ex_putchar(*s++);
338466Smark 		alen--;
339466Smark 		width--;
340466Smark 	}
341466Smark 	npad = width - alen;
342466Smark 
343466Smark 	/* emit any leading pad characters */
344466Smark 	if (!sign)
345466Smark 		while (--npad >= 0)
34630596Sconrad 			ex_putchar(cfill);
347466Smark 
348466Smark 	/* emit the string itself */
349466Smark 	while (--alen >= 0)
35030596Sconrad 		ex_putchar(*s++);
351466Smark 
352466Smark 	/* emit trailing pad characters */
353466Smark 	if (sign)
354466Smark 		while (--npad >= 0)
35530596Sconrad 			ex_putchar(cfill);
356466Smark }
357