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