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