xref: /netbsd-src/usr.bin/printf/printf.c (revision 448e711c7835101c94f75b7ebddf58046df58290)
1 /*
2  * Copyright (c) 1989 The Regents of the University of California.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *	This product includes software developed by the University of
16  *	California, Berkeley and its contributors.
17  * 4. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  */
33 
34 #ifndef lint
35 #if !defined(SHELL) && !defined(BUILTIN)
36 char copyright[] =
37 "@(#) Copyright (c) 1989 The Regents of the University of California.\n\
38  All rights reserved.\n";
39 #endif
40 #endif /* not lint */
41 
42 #ifndef lint
43 /*static char sccsid[] = "from: @(#)printf.c	5.9 (Berkeley) 6/1/90";*/
44 static char rcsid[] = "$Id: printf.c,v 1.9 1993/11/25 04:42:11 jtc Exp $";
45 #endif /* not lint */
46 
47 #include <ctype.h>
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <string.h>
51 #include <limits.h>
52 #include <locale.h>
53 #include <err.h>
54 
55 static int	 print_escape_str __P((const char *));
56 static int	 print_escape __P((const char *));
57 
58 static int	 getchr __P((void));
59 static double	 getdouble __P((void));
60 static int	 getint __P((void));
61 static long	 getlong __P((void));
62 static char	*getstr __P((void));
63 static char	*mklong __P((char *, int));
64 static void	 usage __P((void));
65 
66 static int	rval;
67 static char  **gargv;
68 
69 #define isodigit(c)	((c) >= '0' && (c) <= '7')
70 #define octtobin(c)	((c) - '0')
71 #define hextobin(c)	((c) >= 'A' && (c) <= 'F' ? c - 'A' + 10 : (c) >= 'a' && (c) <= 'f' ? c - 'a' + 10 : c - '0')
72 
73 #ifdef SHELL
74 #define main printfcmd
75 #include "../../bin/sh/bltin/bltin.h"
76 
77 #ifdef __STDC__
78 #include <stdarg.h>
79 #else
80 #include <vararg.h>
81 #endif
82 
83 static void
84 #ifdef __STDC__
85 warnx(const char *fmt, ...)
86 #else
87 warnx(fmt, va_alist)
88 	const char *fmt;
89 	va_dcl
90 #endif
91 {
92 
93 	char buf[64];
94 	va_list ap;
95 
96 #ifdef __STDC__
97 	va_start(ap, fmt);
98 #else
99 	va_start(ap);
100 #endif
101 	vsprintf(buf, fmt, ap);
102 	va_end(ap);
103 
104 	error(buf);
105 }
106 #endif /* SHELL */
107 
108 #define PF(f, func) { \
109 	if (fieldwidth) \
110 		if (precision) \
111 			(void)printf(f, fieldwidth, precision, func); \
112 		else \
113 			(void)printf(f, fieldwidth, func); \
114 	else if (precision) \
115 		(void)printf(f, precision, func); \
116 	else \
117 		(void)printf(f, func); \
118 }
119 
120 int
121 #ifdef BUILTIN
122 progprintf(argc, argv)
123 #else
124 main(argc, argv)
125 #endif
126 	int argc;
127 	char **argv;
128 {
129 	register char *fmt, *start;
130 	register int fieldwidth, precision;
131 	char convch, nextch;
132 	char *format;
133 	int ch;
134 
135 #if !defined(SHELL) && !defined(BUILTIN)
136 	setlocale (LC_ALL, "");
137 #endif
138 
139 	while ((ch = getopt(argc, argv, "")) != -1) {
140 		switch (ch) {
141 		case '?':
142 		default:
143 			usage();
144 			return (1);
145 		}
146 	}
147 	argc -= optind;
148 	argv += optind;
149 
150 	if (argc < 1) {
151 		usage();
152 		return (1);
153 	}
154 
155 	format = *argv;
156 	gargv = ++argv;
157 
158 #define SKIP1	"#-+ 0"
159 #define SKIP2	"*0123456789"
160 	do {
161 		/*
162 		 * Basic algorithm is to scan the format string for conversion
163 		 * specifications -- once one is found, find out if the field
164 		 * width or precision is a '*'; if it is, gather up value.
165 		 * Note, format strings are reused as necessary to use up the
166 		 * provided arguments, arguments of zero/null string are
167 		 * provided to use up the format string.
168 		 */
169 
170 		/* find next format specification */
171 		for (fmt = format; *fmt; fmt++) {
172 			switch (*fmt) {
173 			case '%':
174 				start = fmt++;
175 
176 				if (*fmt == '%') {
177 					putchar ('%');
178 					break;
179 				} else if (*fmt == 'b') {
180 					char *p = getstr();
181 					if (print_escape_str(p)) {
182 						return (rval);
183 					}
184 					break;
185 				}
186 
187 				/* skip to field width */
188 				for (; index(SKIP1, *fmt); ++fmt) ;
189 				fieldwidth = *fmt == '*' ? getint() : 0;
190 
191 				/* skip to possible '.', get following precision */
192 				for (; index(SKIP2, *fmt); ++fmt) ;
193 				if (*fmt == '.')
194 					++fmt;
195 				precision = *fmt == '*' ? getint() : 0;
196 
197 				for (; index(SKIP2, *fmt); ++fmt) ;
198 				if (!*fmt) {
199 					warnx ("missing format character");
200 					rval = 1;
201 					return;		/* XXX */
202 				}
203 
204 				convch = *fmt;
205 				nextch = *(fmt + 1);
206 				*(fmt + 1) = '\0';
207 				switch(convch) {
208 				case 'c': {
209 					char p = getchr();
210 					PF(start, p);
211 					break;
212 				}
213 				case 's': {
214 					char *p = getstr();
215 					PF(start, p);
216 					break;
217 				}
218 				case 'd':
219 				case 'i':
220 				case 'o':
221 				case 'u':
222 				case 'x':
223 				case 'X': {
224 					char *f = mklong(start, convch);
225 					long p = getlong();
226 					PF(f, p);
227 					break;
228 				}
229 				case 'e':
230 				case 'E':
231 				case 'f':
232 				case 'g':
233 				case 'G': {
234 					double p = getdouble();
235 					PF(start, p);
236 					break;
237 				}
238 				default:
239 					warnx ("%s: invalid directive", start);
240 					rval = 1;
241 				}
242 				*(fmt + 1) = nextch;
243 				break;
244 
245 			case '\\':
246 				fmt += print_escape(fmt);
247 				break;
248 
249 			default:
250 				putchar (*fmt);
251 				break;
252 			}
253 		}
254 	} while (gargv > argv && *gargv);
255 
256 	return (rval);
257 }
258 
259 
260 /*
261  * Print SysV echo(1) style escape string
262  *	Halts processing string and returns 1 if a \c escape is encountered.
263  */
264 static int
265 print_escape_str(str)
266 	register const char *str;
267 {
268 	int value;
269 	int c;
270 
271 	while (*str) {
272 		if (*str == '\\') {
273 			str++;
274 			/*
275 			 * %b string octal constants are not like those in C.
276 			 * They start with a \0, and are followed by 0, 1, 2,
277 			 * or 3 octal digits.
278 			 */
279 			if (*str == '0') {
280 				str++;
281 				for (c = 3, value = 0; c-- && isodigit(*str); str++) {
282 					value <<= 3;
283 					value += octtobin(*str);
284 				}
285 				putchar (value);
286 				str--;
287 			} else if (*str == 'c') {
288 				return 1;
289 			} else {
290 				str--;
291 				str += print_escape(str);
292 			}
293 		} else {
294 			putchar (*str);
295 		}
296 		str++;
297 	}
298 
299 	return 0;
300 }
301 
302 /*
303  * Print "standard" escape characters
304  */
305 static int
306 print_escape(str)
307 	register const char *str;
308 {
309 	const char *start = str;
310 	int value;
311 	int c;
312 
313 	str++;
314 
315 	switch (*str) {
316 	case '0': case '1': case '2': case '3':
317 	case '4': case '5': case '6': case '7':
318 		for (c = 3, value = 0; c-- && isodigit(*str); str++) {
319 			value <<= 3;
320 			value += octtobin(*str);
321 		}
322 		putchar(value);
323 		return str - start - 1;
324 		/* NOTREACHED */
325 
326 	case 'x':
327 		str++;
328 		for (value = 0; isxdigit(*str); str++) {
329 			value <<= 4;
330 			value += hextobin(*str);
331 		}
332 		if (value > UCHAR_MAX) {
333 			warnx ("escape sequence out of range for character");
334 			rval = 1;
335 		}
336 		putchar (value);
337 		return str - start - 1;
338 		/* NOTREACHED */
339 
340 	case '\\':			/* backslash */
341 		putchar('\\');
342 		break;
343 
344 	case '\'':			/* single quote */
345 		putchar('\'');
346 		break;
347 
348 	case '"':			/* double quote */
349 		putchar('"');
350 		break;
351 
352 	case 'a':			/* alert */
353 #ifdef __STDC__
354 		putchar('\a');
355 #else
356 		putchar(007);
357 #endif
358 		break;
359 
360 	case 'b':			/* backspace */
361 		putchar('\b');
362 		break;
363 
364 	case 'e':			/* escape */
365 #ifdef __GNUC__
366 		putchar('\e');
367 #else
368 		putchar(033);
369 #endif
370 		break;
371 
372 	case 'f':			/* form-feed */
373 		putchar('\f');
374 		break;
375 
376 	case 'n':			/* newline */
377 		putchar('\n');
378 		break;
379 
380 	case 'r':			/* carriage-return */
381 		putchar('\r');
382 		break;
383 
384 	case 't':			/* tab */
385 		putchar('\t');
386 		break;
387 
388 	case 'v':			/* vertical-tab */
389 		putchar('\v');
390 		break;
391 
392 	default:
393 		putchar(*str);
394 		warnx("unknown escape sequence `\\%c'", *str);
395 		rval = 1;
396 	}
397 
398 	return 1;
399 }
400 
401 static char *
402 mklong(str, ch)
403 	char *str, ch;
404 {
405 	static char copy[64];
406 	int len;
407 
408 	len = strlen(str) + 2;
409 	(void) memmove(copy, str, len - 3);
410 	copy[len - 3] = 'l';
411 	copy[len - 2] = ch;
412 	copy[len - 1] = '\0';
413 	return (copy);
414 }
415 
416 static int
417 getchr()
418 {
419 	if (!*gargv)
420 		return((int)'\0');
421 	return((int)**gargv++);
422 }
423 
424 static char *
425 getstr()
426 {
427 	if (!*gargv)
428 		return("");
429 	return(*gargv++);
430 }
431 
432 static char *number = "+-.0123456789";
433 static int
434 getint()
435 {
436 	if (!*gargv)
437 		return(0);
438 
439 	if (index(number, **gargv))
440 		return(atoi(*gargv++));
441 
442 	return 0;
443 }
444 
445 static long
446 getlong()
447 {
448 	long val;
449 	char *ep;
450 
451 	if (!*gargv)
452 		return(0L);
453 
454 	if (**gargv == '\"' || **gargv == '\'') {
455 		val = gargv[0][1];
456 		gargv++;
457 		return val;
458 	}
459 
460 	val = strtol (*gargv, &ep, 0);
461 	if (*ep) {
462 		warnx ("incompletely converted argument: %s", *gargv);
463 		rval = 1;
464 	}
465 
466 	gargv++;
467 	return val;
468 }
469 
470 static double
471 getdouble()
472 {
473 	double val;
474 	char *ep;
475 
476 	if (!*gargv)
477 		return(0.0);
478 
479 	if (**gargv == '\"' || **gargv == '\'') {
480 		val = gargv[0][1];
481 		gargv++;
482 		return val;
483 	}
484 
485 	val = strtod (*gargv, &ep);
486 	if (*ep) {
487 		warnx ("incompletely converted argument: %s", *gargv);
488 		rval = 1;
489 	}
490 
491 	gargv++;
492 	return val;
493 }
494 
495 static void
496 usage()
497 {
498 	(void)fprintf(stderr, "usage: printf format [arg ...]\n");
499 }
500