1 /* printf.c - Format and Print the data.
2  *
3  * Copyright 2014 Sandeep Sharma <sandeep.jack2756@gmail.com>
4  * Copyright 2014 Kyungwan Han <asura321@gmail.com>
5  *
6  * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/printf.html
7  *
8  * todo: *m$ ala printf("%1$d:%2$.*3$d:%4$.*3$d\n", hour, min, precision, sec);
9 
10 USE_PRINTF(NEWTOY(printf, "<1?^", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_MAYFORK))
11 
12 config PRINTF
13   bool "printf"
14   default y
15   help
16     usage: printf FORMAT [ARGUMENT...]
17 
18     Format and print ARGUMENT(s) according to FORMAT, using C printf syntax
19     (% escapes for cdeEfgGiosuxX, \ escapes for abefnrtv0 or \OCTAL or \xHEX).
20 */
21 
22 #define FOR_printf
23 #include "toys.h"
24 
25 // Detect matching character (return true/false) and advance pointer if match.
eat(char ** s,char c)26 static int eat(char **s, char c)
27 {
28   int x = (**s == c);
29 
30   if (x) ++*s;
31 
32   return x;
33 }
34 
35 // Parse escape sequences.
handle_slash(char ** esc_val,int posix)36 static int handle_slash(char **esc_val, int posix)
37 {
38   char *ptr = *esc_val;
39   int len, base = 0;
40   unsigned result = 0, num;
41 
42   if (*ptr == 'c') xexit();
43 
44   // 0x12 hex escapes have 1-2 digits, \123 octal escapes have 1-3 digits.
45   if (eat(&ptr, 'x')) base = 16;
46   else {
47     if (posix && *ptr=='0') ptr++;
48     if (*ptr >= '0' && *ptr <= '7') base = 8;
49   }
50   len = (char []){0,3,2}[base/8];
51 
52   // Not a hex or octal escape? (This catches trailing \)
53   if (!len) {
54     if (!(result = unescape(*ptr))) result = '\\';
55     else ++*esc_val;
56 
57     return result;
58   }
59 
60   while (len) {
61     num = tolower(*ptr) - '0';
62     if (num >= 'a'-'0') num += '0'-'a'+10;
63     if (num >= base) {
64       // "\xav" is "\xa"+"v", but "\xva" is an error.
65       if (base == 16 && len == 2) error_exit("bad \\x");
66       break;
67     }
68     result = (result*base)+num;
69     ptr++;
70     len--;
71   }
72   *esc_val = ptr;
73 
74   return result;
75 }
76 
printf_main(void)77 void printf_main(void)
78 {
79   char **arg = toys.optargs+1;
80 
81   // Repeat format until arguments consumed
82   for (;;) {
83     int seen = 0;
84     char *f = *toys.optargs;
85 
86     // Loop through characters in format
87     while (*f) {
88       if (eat(&f, '\\')) putchar(handle_slash(&f, 0));
89       else if (!eat(&f, '%') || *f == '%') putchar(*f++);
90 
91       // Handle %escape
92       else {
93         char c, *end = 0, *aa, *to = toybuf;
94         int wp[] = {0,-1}, i = 0;
95 
96         // Parse width.precision between % and type indicator.
97         *to++ = '%';
98         while (strchr("-+# '0", *f) && (to-toybuf)<10) *to++ = *f++;
99         for (;;) {
100           if (eat(&f, '*')) {
101             if (*arg) wp[i] = atolx(*arg++);
102           } else while (*f >= '0' && *f <= '9') wp[i] = (wp[i]*10)+(*f++)-'0';
103           if (i++ || !eat(&f, '.')) break;
104           wp[1] = 0;
105         }
106         c = *f++;
107         seen = sprintf(to, "*.*%c", c);;
108         errno = 0;
109         aa = *arg ? *arg++ : "";
110 
111         // Output %esc using parsed format string
112         if (c == 'b') {
113           while (*aa) putchar(eat(&aa, '\\') ? handle_slash(&aa, 1) : *aa++);
114 
115           continue;
116         } else if (c == 'c') printf(toybuf, wp[0], wp[1], *aa);
117         else if (c == 's') printf(toybuf, wp[0], wp[1], aa);
118         else if (strchr("diouxX", c)) {
119           long long ll;
120 
121           if (*aa == '\'' || *aa == '"') ll = aa[1];
122           else ll = strtoll(aa, &end, 0);
123 
124           sprintf(to, "*.*ll%c", c);
125           printf(toybuf, wp[0], wp[1], ll);
126         } else if (strchr("feEgG", c)) {
127           long double ld = strtold(aa, &end);
128 
129           sprintf(to, "*.*L%c", c);
130           printf(toybuf, wp[0], wp[1], ld);
131         } else error_exit("bad %%%c@%ld", c, (long)(f-*toys.optargs));
132 
133         if (end && (errno || *end)) perror_msg("bad %%%c %s", c, aa);
134       }
135     }
136 
137     // Posix says to keep looping through format until we consume all args.
138     // This only works if the format actually consumed at least one arg.
139     if (!seen || !*arg) break;
140   }
141 }
142