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