xref: /original-bsd/usr.bin/ex/printf.c (revision 7e7b101a)
1 /* The pwb version this is based on */
2 static char *printf_id = "@(#) printf.c:2.2 6/5/79";
3 /* The local sccs version within ex */
4 static char *sccsid = "@(#)printf.c	7.1	07/08/81";
5 #include "varargs.h"
6 /*
7  * This version of printf is compatible with the Version 7 C
8  * printf. The differences are only minor except that this
9  * printf assumes it is to print through putchar. Version 7
10  * printf is more general (and is much larger) and includes
11  * provisions for floating point.
12  */
13 
14 
15 #define MAXOCT	11	/* Maximum octal digits in a long */
16 #define MAXINT	32767	/* largest normal length positive integer */
17 #define BIG	1000000000  /* largest power of 10 less than an unsigned long */
18 #define MAXDIGS	10	/* number of digits in BIG */
19 
20 static int width, sign, fill;
21 
22 char *_p_dconv();
23 
24 printf(va_alist)
25 	va_dcl
26 {
27 	va_list ap;
28 	register char *fmt;
29 	char fcode;
30 	int prec;
31 	int length,mask1,nbits,n;
32 	long int mask2, num;
33 	register char *bptr;
34 	char *ptr;
35 	char buf[134];
36 
37 	va_start(ap);
38 	fmt = va_arg(ap,char *);
39 	for (;;) {
40 		/* process format string first */
41 		while ((fcode = *fmt++)!='%') {
42 			/* ordinary (non-%) character */
43 			if (fcode=='\0')
44 				return;
45 			putchar(fcode);
46 		}
47 		/* length modifier: -1 for h, 1 for l, 0 for none */
48 		length = 0;
49 		/* check for a leading - sign */
50 		sign = 0;
51 		if (*fmt == '-') {
52 			sign++;
53 			fmt++;
54 		}
55 		/* a '0' may follow the - sign */
56 		/* this is the requested fill character */
57 		fill = 1;
58 		if (*fmt == '0') {
59 			fill--;
60 			fmt++;
61 		}
62 
63 		/* Now comes a digit string which may be a '*' */
64 		if (*fmt == '*') {
65 			width = va_arg(ap, int);
66 			if (width < 0) {
67 				width = -width;
68 				sign = !sign;
69 			}
70 			fmt++;
71 		}
72 		else {
73 			width = 0;
74 			while (*fmt>='0' && *fmt<='9')
75 				width = width * 10 + (*fmt++ - '0');
76 		}
77 
78 		/* maybe a decimal point followed by more digits (or '*') */
79 		if (*fmt=='.') {
80 			if (*++fmt == '*') {
81 				prec = va_arg(ap, int);
82 				fmt++;
83 			}
84 			else {
85 				prec = 0;
86 				while (*fmt>='0' && *fmt<='9')
87 					prec = prec * 10 + (*fmt++ - '0');
88 			}
89 		}
90 		else
91 			prec = -1;
92 
93 		/*
94 		 * At this point, "sign" is nonzero if there was
95 		 * a sign, "fill" is 0 if there was a leading
96 		 * zero and 1 otherwise, "width" and "prec"
97 		 * contain numbers corresponding to the digit
98 		 * strings before and after the decimal point,
99 		 * respectively, and "fmt" addresses the next
100 		 * character after the whole mess. If there was
101 		 * no decimal point, "prec" will be -1.
102 		 */
103 		switch (*fmt) {
104 			case 'L':
105 			case 'l':
106 				length = 2;
107 				/* no break!! */
108 			case 'h':
109 			case 'H':
110 				length--;
111 				fmt++;
112 				break;
113 		}
114 
115 		/*
116 		 * At exit from the following switch, we will
117 		 * emit the characters starting at "bptr" and
118 		 * ending at "ptr"-1, unless fcode is '\0'.
119 		 */
120 		switch (fcode = *fmt++) {
121 			/* process characters and strings first */
122 			case 'c':
123 				buf[0] = va_arg(ap, int);
124 				ptr = bptr = &buf[0];
125 				if (buf[0] != '\0')
126 					ptr++;
127 				break;
128 			case 's':
129 				bptr = va_arg(ap,char *);
130 				if (bptr==0)
131 					bptr = "(null pointer)";
132 				if (prec < 0)
133 					prec = MAXINT;
134 				for (n=0; *bptr++ && n < prec; n++) ;
135 				ptr = --bptr;
136 				bptr -= n;
137 				break;
138 			case 'O':
139 				length = 1;
140 				fcode = 'o';
141 				/* no break */
142 			case 'o':
143 			case 'X':
144 			case 'x':
145 				if (length > 0)
146 					num = va_arg(ap,long);
147 				else
148 					num = (unsigned)va_arg(ap,int);
149 				if (fcode=='o') {
150 					mask1 = 0x7;
151 					mask2 = 0x1fffffffL;
152 					nbits = 3;
153 				}
154 				else {
155 					mask1 = 0xf;
156 					mask2 = 0x0fffffffL;
157 					nbits = 4;
158 				}
159 				n = (num!=0);
160 				bptr = buf + MAXOCT + 3;
161 				/* shift and mask for speed */
162 				do
163 				    if (((int) num & mask1) < 10)
164 					*--bptr = ((int) num & mask1) + 060;
165 				    else
166 					*--bptr = ((int) num & mask1) + 0127;
167 				while (num = (num >> nbits) & mask2);
168 
169 				if (fcode=='o') {
170 					if (n)
171 						*--bptr = '0';
172 				}
173 				else
174 					if (!sign && fill <= 0) {
175 						putchar('0');
176 						putchar(fcode);
177 						width -= 2;
178 					}
179 					else {
180 						*--bptr = fcode;
181 						*--bptr = '0';
182 					}
183 				ptr = buf + MAXOCT + 3;
184 				break;
185 			case 'D':
186 			case 'U':
187 			case 'I':
188 				length = 1;
189 				fcode = fcode + 'a' - 'A';
190 				/* no break */
191 			case 'd':
192 			case 'i':
193 			case 'u':
194 				if (length > 0)
195 					num = va_arg(ap,long);
196 				else {
197 					n = va_arg(ap,int);
198 					if (fcode=='u')
199 						num = (unsigned) n;
200 					else
201 						num = (long) n;
202 				}
203 				if (n = (fcode != 'u' && num < 0))
204 					num = -num;
205 				/* now convert to digits */
206 				bptr = _p_dconv(num, buf);
207 				if (n)
208 					*--bptr = '-';
209 				if (fill == 0)
210 					fill = -1;
211 				ptr = buf + MAXDIGS + 1;
212 				break;
213 			default:
214 				/* not a control character,
215 				 * print it.
216 				 */
217 				ptr = bptr = &fcode;
218 				ptr++;
219 				break;
220 			}
221 			if (fcode != '\0')
222 				_p_emit(bptr,ptr);
223 	}
224 	va_end(ap);
225 }
226 
227 /* _p_dconv converts the unsigned long integer "value" to
228  * printable decimal and places it in "buffer", right-justified.
229  * The value returned is the address of the first non-zero character,
230  * or the address of the last character if all are zero.
231  * The result is NOT null terminated, and is MAXDIGS characters long,
232  * starting at buffer[1] (to allow for insertion of a sign).
233  *
234  * This program assumes it is running on 2's complement machine
235  * with reasonable overflow treatment.
236  */
237 char *
238 _p_dconv(value, buffer)
239 	long value;
240 	char *buffer;
241 {
242 	register char *bp;
243 	register int svalue;
244 	int n;
245 	long lval;
246 
247 	bp = buffer;
248 
249 	/* zero is a special case */
250 	if (value == 0) {
251 		bp += MAXDIGS;
252 		*bp = '0';
253 		return(bp);
254 	}
255 
256 	/* develop the leading digit of the value in "n" */
257 	n = 0;
258 	while (value < 0) {
259 		value -= BIG;	/* will eventually underflow */
260 		n++;
261 	}
262 	while ((lval = value - BIG) >= 0) {
263 		value = lval;
264 		n++;
265 	}
266 
267 	/* stash it in buffer[1] to allow for a sign */
268 	bp[1] = n + '0';
269 	/*
270 	 * Now develop the rest of the digits. Since speed counts here,
271 	 * we do it in two loops. The first gets "value" down until it
272 	 * is no larger than MAXINT. The second one uses integer divides
273 	 * rather than long divides to speed it up.
274 	 */
275 	bp += MAXDIGS + 1;
276 	while (value > MAXINT) {
277 		*--bp = (int)(value % 10) + '0';
278 		value /= 10;
279 	}
280 
281 	/* cannot lose precision */
282 	svalue = value;
283 	while (svalue > 0) {
284 		*--bp = (svalue % 10) + '0';
285 		svalue /= 10;
286 	}
287 
288 	/* fill in intermediate zeroes if needed */
289 	if (buffer[1] != '0') {
290 		while (bp > buffer + 2)
291 			*--bp = '0';
292 		--bp;
293 	}
294 	return(bp);
295 }
296 
297 /*
298  * This program sends string "s" to putchar. The character after
299  * the end of "s" is given by "send". This allows the size of the
300  * field to be computed; it is stored in "alen". "width" contains the
301  * user specified length. If width<alen, the width will be taken to
302  * be alen. "sign" is zero if the string is to be right-justified
303  * in the field, nonzero if it is to be left-justified. "fill" is
304  * 0 if the string is to be padded with '0', positive if it is to be
305  * padded with ' ', and negative if an initial '-' should appear before
306  * any padding in right-justification (to avoid printing "-3" as
307  * "000-3" where "-0003" was intended).
308  */
309 _p_emit(s, send)
310 	register char *s;
311 	char *send;
312 {
313 	char cfill;
314 	register int alen;
315 	int npad;
316 
317 	alen = send - s;
318 	if (alen > width)
319 		width = alen;
320 	cfill = fill>0? ' ': '0';
321 
322 	/* we may want to print a leading '-' before anything */
323 	if (*s == '-' && fill < 0) {
324 		putchar(*s++);
325 		alen--;
326 		width--;
327 	}
328 	npad = width - alen;
329 
330 	/* emit any leading pad characters */
331 	if (!sign)
332 		while (--npad >= 0)
333 			putchar(cfill);
334 
335 	/* emit the string itself */
336 	while (--alen >= 0)
337 		putchar(*s++);
338 
339 	/* emit trailing pad characters */
340 	if (sign)
341 		while (--npad >= 0)
342 			putchar(cfill);
343 }
344