Home | History | Annotate | Download | only in posix
      1 /* printf.c - Format and Print the data.
      2  *
      3  * Copyright 2014 Sandeep Sharma <sandeep.jack2756 (at) gmail.com>
      4  * Copyright 2014 Kyungwan Han <asura321 (at) 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 
     10 USE_PRINTF(NEWTOY(printf, "<1?^", TOYFLAG_USR|TOYFLAG_BIN))
     11 
     12 config 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.
     26 static 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.
     36 static 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 
     77 void 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 = 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) : *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 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 && (errno || *end)) 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