printf.c revision 1.18 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