xref: /netbsd/usr.bin/printf/printf.c (revision bf9ec67e)
1 /*	$NetBSD: printf.c,v 1.22 2001/05/05 17:29:39 kleink 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.22 2001/05/05 17:29:39 kleink Exp $");
49 #endif
50 #endif /* not lint */
51 
52 #include <sys/types.h>
53 
54 #include <ctype.h>
55 #include <err.h>
56 #include <errno.h>
57 #include <inttypes.h>
58 #include <limits.h>
59 #include <locale.h>
60 #include <stdio.h>
61 #include <stdlib.h>
62 #include <unistd.h>
63 #include <string.h>
64 #include <unistd.h>
65 
66 static int	 print_escape_str __P((const char *));
67 static size_t	 print_escape __P((const char *));
68 
69 static int	 getchr __P((void));
70 static double	 getdouble __P((void));
71 static int	 getint __P((void));
72 static intmax_t	 getintmax __P((void));
73 static uintmax_t getuintmax __P ((void));
74 static char	*getstr __P((void));
75 static char	*mklong __P((const char *, int));
76 static void      check_conversion __P((const char *, const char *));
77 static void	 usage __P((void));
78 
79 static int	rval;
80 static char  **gargv;
81 
82 #ifdef BUILTIN
83 int progprintf __P((int, char **));
84 #else
85 int main __P((int, char **));
86 #endif
87 
88 #define isodigit(c)	((c) >= '0' && (c) <= '7')
89 #define octtobin(c)	((c) - '0')
90 #define hextobin(c)	((c) >= 'A' && (c) <= 'F' ? c - 'A' + 10 : (c) >= 'a' && (c) <= 'f' ? c - 'a' + 10 : c - '0')
91 
92 #ifdef SHELL
93 #define main printfcmd
94 #include "../../bin/sh/bltin/bltin.h"
95 
96 #ifdef __STDC__
97 #include <stdarg.h>
98 #else
99 #include <vararg.h>
100 #endif
101 
102 static void warnx __P((const char *fmt, ...));
103 
104 static void
105 #ifdef __STDC__
106 warnx(const char *fmt, ...)
107 #else
108 warnx(fmt, va_alist)
109 	const char *fmt;
110 	va_dcl
111 #endif
112 {
113 
114 	char buf[64];
115 	va_list ap;
116 
117 #ifdef __STDC__
118 	va_start(ap, fmt);
119 #else
120 	va_start(ap);
121 #endif
122 	vsprintf(buf, fmt, ap);
123 	va_end(ap);
124 
125 	error(buf);
126 }
127 #endif /* SHELL */
128 
129 #define PF(f, func) { \
130 	if (fieldwidth) { \
131 		if (precision) \
132 			(void)printf(f, fieldwidth, precision, func); \
133 		else \
134 			(void)printf(f, fieldwidth, func); \
135 	} else if (precision) \
136 		(void)printf(f, precision, func); \
137 	else \
138 		(void)printf(f, func); \
139 }
140 
141 int
142 #ifdef BUILTIN
143 progprintf(argc, argv)
144 #else
145 main(argc, argv)
146 #endif
147 	int argc;
148 	char *argv[];
149 {
150 	char *fmt, *start;
151 	int fieldwidth, precision;
152 	char convch, nextch;
153 	char *format;
154 	int ch;
155 
156 #if !defined(SHELL) && !defined(BUILTIN)
157 	(void)setlocale (LC_ALL, "");
158 #endif
159 
160 	while ((ch = getopt(argc, argv, "")) != -1) {
161 		switch (ch) {
162 		case '?':
163 		default:
164 			usage();
165 			return (1);
166 		}
167 	}
168 	argc -= optind;
169 	argv += optind;
170 
171 	if (argc < 1) {
172 		usage();
173 		return (1);
174 	}
175 
176 	format = *argv;
177 	gargv = ++argv;
178 
179 #define SKIP1	"#-+ 0"
180 #define SKIP2	"*0123456789"
181 	do {
182 		/*
183 		 * Basic algorithm is to scan the format string for conversion
184 		 * specifications -- once one is found, find out if the field
185 		 * width or precision is a '*'; if it is, gather up value.
186 		 * Note, format strings are reused as necessary to use up the
187 		 * provided arguments, arguments of zero/null string are
188 		 * provided to use up the format string.
189 		 */
190 
191 		/* find next format specification */
192 		for (fmt = format; *fmt; fmt++) {
193 			switch (*fmt) {
194 			case '%':
195 				start = fmt++;
196 
197 				if (*fmt == '%') {
198 					(void)putchar('%');
199 					break;
200 				} else if (*fmt == 'b') {
201 					char *p = getstr();
202 					if (print_escape_str(p)) {
203 						return (rval);
204 					}
205 					break;
206 				}
207 
208 				/* skip to field width */
209 				for (; strchr(SKIP1, *fmt); ++fmt) ;
210 				fieldwidth = *fmt == '*' ? getint() : 0;
211 
212 				/* skip to possible '.', get following precision */
213 				for (; strchr(SKIP2, *fmt); ++fmt) ;
214 				if (*fmt == '.')
215 					++fmt;
216 				precision = *fmt == '*' ? getint() : 0;
217 
218 				for (; strchr(SKIP2, *fmt); ++fmt) ;
219 				if (!*fmt) {
220 					warnx ("missing format character");
221 					return(1);
222 				}
223 
224 				convch = *fmt;
225 				nextch = *(fmt + 1);
226 				*(fmt + 1) = '\0';
227 				switch(convch) {
228 				case 'c': {
229 					char p = getchr();
230 					PF(start, p);
231 					break;
232 				}
233 				case 's': {
234 					char *p = getstr();
235 					PF(start, p);
236 					break;
237 				}
238 				case 'd':
239 				case 'i': {
240 					char *f = mklong(start, convch);
241 					intmax_t p = getintmax();
242 					PF(f, p);
243 					break;
244 				}
245 				case 'o':
246 				case 'u':
247 				case 'x':
248 				case 'X': {
249 					char *f = mklong(start, convch);
250 					uintmax_t p = getuintmax();
251 					PF(f, p);
252 					break;
253 				}
254 				case 'e':
255 				case 'E':
256 				case 'f':
257 				case 'g':
258 				case 'G': {
259 					double p = getdouble();
260 					PF(start, p);
261 					break;
262 				}
263 				default:
264 					warnx ("%s: invalid directive", start);
265 					return(1);
266 				}
267 				*(fmt + 1) = nextch;
268 				break;
269 
270 			case '\\':
271 				fmt += print_escape(fmt);
272 				break;
273 
274 			default:
275 				(void)putchar(*fmt);
276 				break;
277 			}
278 		}
279 	} while (gargv > argv && *gargv);
280 
281 	return (rval);
282 }
283 
284 
285 /*
286  * Print SysV echo(1) style escape string
287  *	Halts processing string and returns 1 if a \c escape is encountered.
288  */
289 static int
290 print_escape_str(str)
291 	const char *str;
292 {
293 	int value;
294 	int c;
295 
296 	while (*str) {
297 		if (*str == '\\') {
298 			str++;
299 			/*
300 			 * %b string octal constants are not like those in C.
301 			 * They start with a \0, and are followed by 0, 1, 2,
302 			 * or 3 octal digits.
303 			 */
304 			if (*str == '0') {
305 				str++;
306 				for (c = 3, value = 0; c-- && isodigit(*str); str++) {
307 					value <<= 3;
308 					value += octtobin(*str);
309 				}
310 				(void)putchar(value);
311 				str--;
312 			} else if (*str == 'c') {
313 				return 1;
314 			} else {
315 				str--;
316 				str += print_escape(str);
317 			}
318 		} else {
319 			(void)putchar(*str);
320 		}
321 		str++;
322 	}
323 
324 	return 0;
325 }
326 
327 /*
328  * Print "standard" escape characters
329  */
330 static size_t
331 print_escape(str)
332 	const char *str;
333 {
334 	const char *start = str;
335 	int value;
336 	int c;
337 
338 	str++;
339 
340 	switch (*str) {
341 	case '0': case '1': case '2': case '3':
342 	case '4': case '5': case '6': case '7':
343 		for (c = 3, value = 0; c-- && isodigit(*str); str++) {
344 			value <<= 3;
345 			value += octtobin(*str);
346 		}
347 		(void)putchar(value);
348 		return str - start - 1;
349 		/* NOTREACHED */
350 
351 	case 'x':
352 		str++;
353 		for (value = 0; isxdigit((unsigned char)*str); str++) {
354 			value <<= 4;
355 			value += hextobin(*str);
356 		}
357 		if (value > UCHAR_MAX) {
358 			warnx ("escape sequence out of range for character");
359 			rval = 1;
360 		}
361 		(void)putchar (value);
362 		return str - start - 1;
363 		/* NOTREACHED */
364 
365 	case '\\':			/* backslash */
366 		(void)putchar('\\');
367 		break;
368 
369 	case '\'':			/* single quote */
370 		(void)putchar('\'');
371 		break;
372 
373 	case '"':			/* double quote */
374 		(void)putchar('"');
375 		break;
376 
377 	case 'a':			/* alert */
378 #ifdef __STDC__
379 		(void)putchar('\a');
380 #else
381 		(void)putchar(007);
382 #endif
383 		break;
384 
385 	case 'b':			/* backspace */
386 		(void)putchar('\b');
387 		break;
388 
389 	case 'e':			/* escape */
390 #ifdef __GNUC__
391 		(void)putchar('\e');
392 #else
393 		(void)putchar(033);
394 #endif
395 		break;
396 
397 	case 'f':			/* form-feed */
398 		(void)putchar('\f');
399 		break;
400 
401 	case 'n':			/* newline */
402 		(void)putchar('\n');
403 		break;
404 
405 	case 'r':			/* carriage-return */
406 		(void)putchar('\r');
407 		break;
408 
409 	case 't':			/* tab */
410 		(void)putchar('\t');
411 		break;
412 
413 	case 'v':			/* vertical-tab */
414 		(void)putchar('\v');
415 		break;
416 
417 	default:
418 		(void)putchar(*str);
419 		warnx("unknown escape sequence `\\%c'", *str);
420 		rval = 1;
421 		break;
422 	}
423 
424 	return 1;
425 }
426 
427 static char *
428 mklong(str, ch)
429 	const char *str;
430 	char ch;
431 {
432 	static char copy[64];
433 	size_t len;
434 
435 	len = strlen(str) + 2;
436 	(void)memmove(copy, str, len - 3);
437 	copy[len - 3] = 'j';
438 	copy[len - 2] = ch;
439 	copy[len - 1] = '\0';
440 	return (copy);
441 }
442 
443 static int
444 getchr()
445 {
446 	if (!*gargv)
447 		return ('\0');
448 	return ((int)**gargv++);
449 }
450 
451 static char *
452 getstr()
453 {
454 	if (!*gargv)
455 		return ("");
456 	return (*gargv++);
457 }
458 
459 static char *Number = "+-.0123456789";
460 static int
461 getint()
462 {
463 	if (!*gargv)
464 		return(0);
465 
466 	if (strchr(Number, **gargv))
467 		return(atoi(*gargv++));
468 
469 	return 0;
470 }
471 
472 static intmax_t
473 getintmax()
474 {
475 	intmax_t val;
476 	char *ep;
477 
478 	if (!*gargv)
479 		return(INTMAX_C(0));
480 
481 	if (**gargv == '\"' || **gargv == '\'')
482 		return (intmax_t) *((*gargv++)+1);
483 
484 	errno = 0;
485 	val = strtoimax (*gargv, &ep, 0);
486 	check_conversion(*gargv++, ep);
487 	return val;
488 }
489 
490 static uintmax_t
491 getuintmax()
492 {
493 	uintmax_t val;
494 	char *ep;
495 
496 	if (!*gargv)
497 		return(UINTMAX_C(0));
498 
499 	if (**gargv == '\"' || **gargv == '\'')
500 		return (uintmax_t) *((*gargv++)+1);
501 
502 	errno = 0;
503 	val = strtoumax (*gargv, &ep, 0);
504 	check_conversion(*gargv++, ep);
505 	return val;
506 }
507 
508 static double
509 getdouble()
510 {
511 	double val;
512 	char *ep;
513 
514 	if (!*gargv)
515 		return(0.0);
516 
517 	if (**gargv == '\"' || **gargv == '\'')
518 		return (double) *((*gargv++)+1);
519 
520 	errno = 0;
521 	val = strtod (*gargv, &ep);
522 	check_conversion(*gargv++, ep);
523 	return val;
524 }
525 
526 static void
527 check_conversion(s, ep)
528 	const char *s;
529 	const char *ep;
530 {
531 	if (*ep) {
532 		if (ep == s)
533 			warnx ("%s: expected numeric value", s);
534 		else
535 			warnx ("%s: not completely converted", s);
536 		rval = 1;
537 	} else if (errno == ERANGE) {
538 		warnx ("%s: %s", s, strerror(ERANGE));
539 		rval = 1;
540 	}
541 }
542 
543 static void
544 usage()
545 {
546 	(void)fprintf(stderr, "usage: printf format [arg ...]\n");
547 }
548