10f66f451Sopenharmony_ci/* printf.c - Format and Print the data.
20f66f451Sopenharmony_ci *
30f66f451Sopenharmony_ci * Copyright 2014 Sandeep Sharma <sandeep.jack2756@gmail.com>
40f66f451Sopenharmony_ci * Copyright 2014 Kyungwan Han <asura321@gmail.com>
50f66f451Sopenharmony_ci *
60f66f451Sopenharmony_ci * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/printf.html
70f66f451Sopenharmony_ci *
80f66f451Sopenharmony_ci * todo: *m$ ala printf("%1$d:%2$.*3$d:%4$.*3$d\n", hour, min, precision, sec);
90f66f451Sopenharmony_ci
100f66f451Sopenharmony_ciUSE_PRINTF(NEWTOY(printf, "<1?^", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_MAYFORK))
110f66f451Sopenharmony_ci
120f66f451Sopenharmony_ciconfig PRINTF
130f66f451Sopenharmony_ci  bool "printf"
140f66f451Sopenharmony_ci  default y
150f66f451Sopenharmony_ci  help
160f66f451Sopenharmony_ci    usage: printf FORMAT [ARGUMENT...]
170f66f451Sopenharmony_ci
180f66f451Sopenharmony_ci    Format and print ARGUMENT(s) according to FORMAT, using C printf syntax
190f66f451Sopenharmony_ci    (% escapes for cdeEfgGiosuxX, \ escapes for abefnrtv0 or \OCTAL or \xHEX).
200f66f451Sopenharmony_ci*/
210f66f451Sopenharmony_ci
220f66f451Sopenharmony_ci#define FOR_printf
230f66f451Sopenharmony_ci#include "toys.h"
240f66f451Sopenharmony_ci
250f66f451Sopenharmony_ci// Detect matching character (return true/false) and advance pointer if match.
260f66f451Sopenharmony_cistatic int eat(char **s, char c)
270f66f451Sopenharmony_ci{
280f66f451Sopenharmony_ci  int x = (**s == c);
290f66f451Sopenharmony_ci
300f66f451Sopenharmony_ci  if (x) ++*s;
310f66f451Sopenharmony_ci
320f66f451Sopenharmony_ci  return x;
330f66f451Sopenharmony_ci}
340f66f451Sopenharmony_ci
350f66f451Sopenharmony_ci// Parse escape sequences.
360f66f451Sopenharmony_cistatic int handle_slash(char **esc_val, int posix)
370f66f451Sopenharmony_ci{
380f66f451Sopenharmony_ci  char *ptr = *esc_val;
390f66f451Sopenharmony_ci  int len, base = 0;
400f66f451Sopenharmony_ci  unsigned result = 0, num;
410f66f451Sopenharmony_ci
420f66f451Sopenharmony_ci  if (*ptr == 'c') xexit();
430f66f451Sopenharmony_ci
440f66f451Sopenharmony_ci  // 0x12 hex escapes have 1-2 digits, \123 octal escapes have 1-3 digits.
450f66f451Sopenharmony_ci  if (eat(&ptr, 'x')) base = 16;
460f66f451Sopenharmony_ci  else {
470f66f451Sopenharmony_ci    if (posix && *ptr=='0') ptr++;
480f66f451Sopenharmony_ci    if (*ptr >= '0' && *ptr <= '7') base = 8;
490f66f451Sopenharmony_ci  }
500f66f451Sopenharmony_ci  len = (char []){0,3,2}[base/8];
510f66f451Sopenharmony_ci
520f66f451Sopenharmony_ci  // Not a hex or octal escape? (This catches trailing \)
530f66f451Sopenharmony_ci  if (!len) {
540f66f451Sopenharmony_ci    if (!(result = unescape(*ptr))) result = '\\';
550f66f451Sopenharmony_ci    else ++*esc_val;
560f66f451Sopenharmony_ci
570f66f451Sopenharmony_ci    return result;
580f66f451Sopenharmony_ci  }
590f66f451Sopenharmony_ci
600f66f451Sopenharmony_ci  while (len) {
610f66f451Sopenharmony_ci    num = tolower(*ptr) - '0';
620f66f451Sopenharmony_ci    if (num >= 'a'-'0') num += '0'-'a'+10;
630f66f451Sopenharmony_ci    if (num >= base) {
640f66f451Sopenharmony_ci      // "\xav" is "\xa"+"v", but "\xva" is an error.
650f66f451Sopenharmony_ci      if (base == 16 && len == 2) error_exit("bad \\x");
660f66f451Sopenharmony_ci      break;
670f66f451Sopenharmony_ci    }
680f66f451Sopenharmony_ci    result = (result*base)+num;
690f66f451Sopenharmony_ci    ptr++;
700f66f451Sopenharmony_ci    len--;
710f66f451Sopenharmony_ci  }
720f66f451Sopenharmony_ci  *esc_val = ptr;
730f66f451Sopenharmony_ci
740f66f451Sopenharmony_ci  return result;
750f66f451Sopenharmony_ci}
760f66f451Sopenharmony_ci
770f66f451Sopenharmony_civoid printf_main(void)
780f66f451Sopenharmony_ci{
790f66f451Sopenharmony_ci  char **arg = toys.optargs+1;
800f66f451Sopenharmony_ci
810f66f451Sopenharmony_ci  // Repeat format until arguments consumed
820f66f451Sopenharmony_ci  for (;;) {
830f66f451Sopenharmony_ci    int seen = 0;
840f66f451Sopenharmony_ci    char *f = *toys.optargs;
850f66f451Sopenharmony_ci
860f66f451Sopenharmony_ci    // Loop through characters in format
870f66f451Sopenharmony_ci    while (*f) {
880f66f451Sopenharmony_ci      if (eat(&f, '\\')) putchar(handle_slash(&f, 0));
890f66f451Sopenharmony_ci      else if (!eat(&f, '%') || *f == '%') putchar(*f++);
900f66f451Sopenharmony_ci
910f66f451Sopenharmony_ci      // Handle %escape
920f66f451Sopenharmony_ci      else {
930f66f451Sopenharmony_ci        char c, *end = 0, *aa, *to = toybuf;
940f66f451Sopenharmony_ci        int wp[] = {0,-1}, i = 0;
950f66f451Sopenharmony_ci
960f66f451Sopenharmony_ci        // Parse width.precision between % and type indicator.
970f66f451Sopenharmony_ci        *to++ = '%';
980f66f451Sopenharmony_ci        while (strchr("-+# '0", *f) && (to-toybuf)<10) *to++ = *f++;
990f66f451Sopenharmony_ci        for (;;) {
1000f66f451Sopenharmony_ci          if (eat(&f, '*')) {
1010f66f451Sopenharmony_ci            if (*arg) wp[i] = atolx(*arg++);
1020f66f451Sopenharmony_ci          } else while (*f >= '0' && *f <= '9') wp[i] = (wp[i]*10)+(*f++)-'0';
1030f66f451Sopenharmony_ci          if (i++ || !eat(&f, '.')) break;
1040f66f451Sopenharmony_ci          wp[1] = 0;
1050f66f451Sopenharmony_ci        }
1060f66f451Sopenharmony_ci        c = *f++;
1070f66f451Sopenharmony_ci        seen = sprintf(to, "*.*%c", c);;
1080f66f451Sopenharmony_ci        errno = 0;
1090f66f451Sopenharmony_ci        aa = *arg ? *arg++ : "";
1100f66f451Sopenharmony_ci
1110f66f451Sopenharmony_ci        // Output %esc using parsed format string
1120f66f451Sopenharmony_ci        if (c == 'b') {
1130f66f451Sopenharmony_ci          while (*aa) putchar(eat(&aa, '\\') ? handle_slash(&aa, 1) : *aa++);
1140f66f451Sopenharmony_ci
1150f66f451Sopenharmony_ci          continue;
1160f66f451Sopenharmony_ci        } else if (c == 'c') printf(toybuf, wp[0], wp[1], *aa);
1170f66f451Sopenharmony_ci        else if (c == 's') printf(toybuf, wp[0], wp[1], aa);
1180f66f451Sopenharmony_ci        else if (strchr("diouxX", c)) {
1190f66f451Sopenharmony_ci          long long ll;
1200f66f451Sopenharmony_ci
1210f66f451Sopenharmony_ci          if (*aa == '\'' || *aa == '"') ll = aa[1];
1220f66f451Sopenharmony_ci          else ll = strtoll(aa, &end, 0);
1230f66f451Sopenharmony_ci
1240f66f451Sopenharmony_ci          sprintf(to, "*.*ll%c", c);
1250f66f451Sopenharmony_ci          printf(toybuf, wp[0], wp[1], ll);
1260f66f451Sopenharmony_ci        } else if (strchr("feEgG", c)) {
1270f66f451Sopenharmony_ci          long double ld = strtold(aa, &end);
1280f66f451Sopenharmony_ci
1290f66f451Sopenharmony_ci          sprintf(to, "*.*L%c", c);
1300f66f451Sopenharmony_ci          printf(toybuf, wp[0], wp[1], ld);
1310f66f451Sopenharmony_ci        } else error_exit("bad %%%c@%ld", c, (long)(f-*toys.optargs));
1320f66f451Sopenharmony_ci
1330f66f451Sopenharmony_ci        if (end && (errno || *end)) perror_msg("bad %%%c %s", c, aa);
1340f66f451Sopenharmony_ci      }
1350f66f451Sopenharmony_ci    }
1360f66f451Sopenharmony_ci
1370f66f451Sopenharmony_ci    // Posix says to keep looping through format until we consume all args.
1380f66f451Sopenharmony_ci    // This only works if the format actually consumed at least one arg.
1390f66f451Sopenharmony_ci    if (!seen || !*arg) break;
1400f66f451Sopenharmony_ci  }
1410f66f451Sopenharmony_ci}
142