aboutsummaryrefslogtreecommitdiff
path: root/toys/posix/printf.c
blob: 5448d8c717336a84d7e2a3c74eb40e8d00d8e26d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
/* 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;
  }
}