printf.c revision 1.23 1 /* $NetBSD: printf.c,v 1.23 2002/06/14 01:12:15 wiz 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.23 2002/06/14 01:12:15 wiz 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(argc, 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