| /* printf.c - Format and Print the data. |
| * |
| * Copyright 2014 Sandeep Sharma <sandeep.jack2756@gmail.com> |
| * Copyright 2014 Kyungwan Han <asura321@gmail.com> |
| * |
| * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/printf.html |
| * |
| * todo: *m$ ala printf("%1$d:%2$.*3$d:%4$.*3$d\n", hour, min, precision, sec); |
| |
| USE_PRINTF(NEWTOY(printf, "<1?^", TOYFLAG_USR|TOYFLAG_BIN)) |
| |
| config PRINTF |
| bool "printf" |
| default y |
| help |
| usage: printf FORMAT [ARGUMENT...] |
| |
| Format and print ARGUMENT(s) according to FORMAT, using C printf syntax |
| (% escapes for cdeEfgGiosuxX, \ escapes for abefnrtv0 or \OCTAL or \xHEX). |
| */ |
| |
| #define FOR_printf |
| #include "toys.h" |
| |
| // Detect matching character (return true/false) and advance pointer if match. |
| static int eat(char **s, char c) |
| { |
| int x = (**s == c); |
| |
| if (x) ++*s; |
| |
| return x; |
| } |
| |
| // Parse escape sequences. |
| static int handle_slash(char **esc_val, int posix) |
| { |
| char *ptr = *esc_val; |
| int len, base = 0; |
| unsigned result = 0, num; |
| |
| if (*ptr == 'c') xexit(); |
| |
| // 0x12 hex escapes have 1-2 digits, \123 octal escapes have 1-3 digits. |
| if (eat(&ptr, 'x')) base = 16; |
| else { |
| if (posix && *ptr=='0') ptr++; |
| if (*ptr >= '0' && *ptr <= '7') base = 8; |
| } |
| len = (char []){0,3,2}[base/8]; |
| |
| // Not a hex or octal escape? (This catches trailing \) |
| if (!len) { |
| if (!(result = unescape(*ptr))) result = '\\'; |
| else ++*esc_val; |
| |
| return result; |
| } |
| |
| while (len) { |
| num = tolower(*ptr) - '0'; |
| if (num >= 'a'-'0') num += '0'-'a'+10; |
| if (num >= base) { |
| // Don't parse invalid hex value ala "\xvd", print it verbatim |
| if (base == 16 && len == 2) { |
| ptr--; |
| result = '\\'; |
| } |
| break; |
| } |
| result = (result*base)+num; |
| ptr++; |
| len--; |
| } |
| *esc_val = ptr; |
| |
| return result; |
| } |
| |
| void printf_main(void) |
| { |
| char **arg = toys.optargs+1; |
| |
| // Repeat format until arguments consumed |
| for (;;) { |
| int seen = 0; |
| char *f = *toys.optargs; |
| |
| // Loop through characters in format |
| while (*f) { |
| if (eat(&f, '\\')) putchar(handle_slash(&f, 0)); |
| else if (!eat(&f, '%') || *f == '%') putchar(*f++); |
| |
| // Handle %escape |
| else { |
| char c, *end = 0, *aa, *to = toybuf; |
| int wp[] = {0,-1}, i = 0; |
| |
| // Parse width.precision between % and type indicator. |
| *to++ = '%'; |
| while (strchr("-+# '0", *f) && (to-toybuf)<10) *to++ = *f++; |
| for (;;) { |
| if (eat(&f, '*')) { |
| if (*arg) wp[i] = atolx(*arg++); |
| } else while (*f >= '0' && *f <= '9') wp[i] = (wp[i]*10)+(*f++)-'0'; |
| if (i++ || !eat(&f, '.')) break; |
| wp[1] = 0; |
| } |
| c = *f++; |
| seen = sprintf(to, "*.*%c", c);; |
| errno = 0; |
| aa = *arg ? *arg++ : ""; |
| |
| // Output %esc using parsed format string |
| if (c == 'b') { |
| while (*aa) putchar(eat(&aa, '\\') ? handle_slash(&aa, 1) : *aa++); |
| |
| continue; |
| } else if (c == 'c') printf(toybuf, wp[0], wp[1], *aa); |
| else if (c == 's') printf(toybuf, wp[0], wp[1], aa); |
| else if (strchr("diouxX", c)) { |
| long long ll; |
| |
| if (*aa == '\'' || *aa == '"') ll = aa[1]; |
| else ll = strtoll(aa, &end, 0); |
| |
| sprintf(to, "*.*ll%c", c); |
| printf(toybuf, wp[0], wp[1], ll); |
| } else if (strchr("feEgG", c)) { |
| long double ld = strtold(aa, &end); |
| |
| sprintf(to, "*.*L%c", c); |
| printf(toybuf, wp[0], wp[1], ld); |
| } else error_exit("bad %%%c@%ld", c, (long)(f-*toys.optargs)); |
| |
| if (end && (errno || *end)) perror_msg("bad %%%c %s", c, aa); |
| } |
| } |
| |
| // Posix says to keep looping through format until we consume all args. |
| // This only works if the format actually consumed at least one arg. |
| if (!seen || !*arg) break; |
| } |
| } |