1 /* printf.c - Format and Print the data.
2 *
3 * Copyright 2014 Sandeep Sharma <[email protected]>
4 * Copyright 2014 Kyungwan Han <[email protected]>
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.
chrstart(char ** s,char c)26 static int chrstart(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 (chrstart(&ptr, 'x')) base = 16;
46 else {
47 if (posix && *ptr=='0') ptr++;
48 if (*ptr >= '0' && *ptr <= '7') base = 8;
49 else if (ptr != *esc_val) goto done;
50 }
51 len = (char []){0,3,2}[base/8];
52
53 // Not a hex or octal escape? (This catches trailing \)
54 if (!len) {
55 if (!(result = unescape(*ptr))) result = '\\';
56 else ++*esc_val;
57
58 return result;
59 }
60
61 while (len) {
62 num = tolower(*ptr) - '0';
63 if (num >= 'a'-'0') num += '0'-'a'+10;
64 if (num >= base) {
65 // "\xav" is "\xa"+"v", but "\xva" is an error.
66 if (base == 16 && len == 2) error_exit("bad \\x");
67 break;
68 }
69 result = (result*base)+num;
70 ptr++;
71 len--;
72 }
73 done:
74 *esc_val = ptr;
75
76 return result;
77 }
78
printf_main(void)79 void printf_main(void)
80 {
81 char **arg = toys.optargs+1;
82
83 // Repeat format until arguments consumed
84 for (;;) {
85 int seen = 0;
86 char *f = *toys.optargs;
87
88 // Loop through characters in format
89 while (*f) {
90 if (chrstart(&f, '\\')) putchar(handle_slash(&f, 0));
91 else if (!chrstart(&f, '%') || *f == '%') putchar(*f++);
92
93 // Handle %escape
94 else {
95 char c, *end = 0, *aa, *to = toybuf;
96 int wp[] = {0,-1}, i = 0;
97
98 // Parse width.precision between % and type indicator.
99 *to++ = '%';
100 while (strchr("-+# '0", *f) && (to-toybuf)<10) *to++ = *f++;
101 for (;;) {
102 if (chrstart(&f, '*')) {
103 if (*arg) wp[i] = atolx(*arg++);
104 } else while (*f >= '0' && *f <= '9') wp[i] = (wp[i]*10)+(*f++)-'0';
105 if (i++ || !chrstart(&f, '.')) break;
106 wp[1] = 0;
107 }
108 c = *f++;
109 seen = sprintf(to, "*.*%c", c);;
110 errno = 0;
111 aa = *arg ? *arg++ : "";
112
113 // Output %esc using parsed format string
114 if (c == 'b') {
115 while (*aa)
116 putchar(chrstart(&aa, '\\') ? handle_slash(&aa, 1) : *aa++);
117
118 continue;
119 } else if (c == 'c') printf(toybuf, wp[0], wp[1], *aa);
120 else if (c == 's') printf(toybuf, wp[0], wp[1], aa);
121 else if (strchr("diouxX", c)) {
122 long long ll;
123
124 if (*aa == '\'' || *aa == '"') ll = aa[1];
125 else ll = strtoll(aa, &end, 0);
126
127 sprintf(to, "*.*ll%c", c);
128 printf(toybuf, wp[0], wp[1], ll);
129 } else if (strchr("feEgG", c)) {
130 long double ld = strtold(aa, &end);
131
132 sprintf(to, "*.*L%c", c);
133 printf(toybuf, wp[0], wp[1], ld);
134 } else error_exit("bad %%%c@%ld", c, (long)(f-*toys.optargs));
135
136 if (end && (*end || errno==ERANGE)) perror_msg("bad %%%c %s", c, aa);
137 }
138 }
139
140 // Posix says to keep looping through format until we consume all args.
141 // This only works if the format actually consumed at least one arg.
142 if (!seen || !*arg) break;
143 }
144 }
145