toybox/toys/posix/printf.c
<<
>>
Prefs
   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|TOYFLAG_MAYFORK))
  11
  12config 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.
  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, 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
  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, 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 && (*end || errno==ERANGE)) 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