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