printf.c revision d0dead30a53c0353fd1c31aa5183fdcc2b30491b
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
10USE_PRINTF(NEWTOY(printf, "<1", TOYFLAG_USR|TOYFLAG_BIN))
11
12config PRINTF
13  bool "printf"
14  default n
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/valse) and advance pointer if match.
26static 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.
36static int handle_slash(char **esc_val)
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 if (*ptr >= '0' && *ptr <= '8') base = 8;
47  len = (char []){0,3,2}[base/8];
48
49  // Not a hex or octal escape? (This catches trailing \)
50  if (!len) {
51    if (!(result = unescape(*ptr))) result = '\\';
52    else ++*esc_val;
53
54    return result;
55  }
56
57  while (len) {
58    num = tolower(*ptr) - '0';
59    if (num >= 'a'-'0') num += '0'-'a'+10;
60    if (num >= base) {
61      // Don't parse invalid hex value ala "\xvd", print it verbatim
62      if (base == 16 && len == 2) {
63        ptr--;
64        result = '\\';
65      }
66      break;
67    }
68    result = (result*base)+num;
69    ptr++;
70    len--;
71  }
72  *esc_val = ptr;
73
74  return result;
75}
76
77void 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));
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;
95
96        // Parse width.precision between % and type indicator.
97        *to++ = '%';
98        while (strchr("-+# '0", *f) && (to-toybuf)<10) *to++ = *f++;
99        for (i=0; i<2; i++) {
100          if (eat(&f, '*')) {
101            if (*arg) wp[i] = atolx(*arg++);
102          } else while (*f >= '0' && *f <= '9') {
103            if (wp[i]<0) wp[i] = 0;
104            wp[i] = (wp[i]*10)+(*f++)-'0';
105          }
106          if (!eat(&f, '.')) break;
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) putchar(eat(&aa, '\\') ? handle_slash(&aa) : *aa++);
116
117          continue;
118        } else if (c == 'c') printf(toybuf, wp[0], wp[1], *aa);
119        else if (c == 's') printf(toybuf, wp[0], wp[1], aa);
120        else if (strchr("diouxX", c)) {
121          long ll;
122
123          if (*aa == '\'' || *aa == '"') ll = aa[1];
124          else ll = strtoll(aa, &end, 0);
125
126          sprintf(to, "*.*ll%c", c);
127          printf(toybuf, wp[0], wp[1], ll);
128        } else if (strchr("feEgG", c)) {
129          long double ld = strtold(aa, &end);
130
131          sprintf(to, "*.*L%c", c);
132          printf(toybuf, wp[0], wp[1], ld);
133        } else error_exit("bad %%%c@%ld", c, f-*toys.optargs);
134
135        if (end && (errno || *end)) perror_msg("bad %%%c %s", c, aa);
136      }
137    }
138
139    // Posix says to keep looping through format until we consume all args.
140    // This only works if the format actually consumed at least one arg.
141    if (!seen || !*arg) break;
142  }
143}
144