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