changeset 1649:66bb2847993d draft

More half-finished cleanup of printf.c, and more test suite entries.
author Rob Landley <rob@landley.net>
date Sat, 10 Jan 2015 20:02:21 -0600
parents d27136a0811b
children a740a876c76c
files tests/printf.test toys/pending/printf.c
diffstat 2 files changed, 77 insertions(+), 107 deletions(-) [+]
line wrap: on
line diff
--- a/tests/printf.test	Tue Jan 06 15:06:51 2015 -0600
+++ b/tests/printf.test	Sat Jan 10 20:02:21 2015 -0600
@@ -21,7 +21,7 @@
 testing "printf not octal" "$PRINTF '\9'" '\9' "" ""
 testing "printf hex" "$PRINTF 'A\x1b\x2B\x3Q\xa' | od -An -tx1" \
   ' 41 1b 2b 03 51 0a\n' "" ""
-testing "printf " "$PRINTF '%x\n' 0x2a" "2a\n" "" ""
+testing "printf %x" "$PRINTF '%x\n' 0x2a" "2a\n" "" ""
 
 testing "printf %d 42" "$PRINTF %d 42" "42" "" ""
 testing "printf %d 0x2a" "$PRINTF %d 0x2a" "42" "" ""
@@ -31,6 +31,14 @@
   "$PRINTF '%3s,%.3s,%10s,%10.3s' abcde fghij klmno pqrst" \
   "abcde,fgh,     klmno,       pqr" "" ""
 
+# posix: "The format operand shall be reused as often as necessary to satisfy
+# the argument operands."
+
+testing "printf extra args" "$PRINTF 'abc%s!%ddef\n' X 42 ARG 36" \
+	"abcX!42def\nabcARG!36def\n" "" ""
+
+testing "printf '%3c'" "printf '%3c' x" "  x" "" ""
+
 testing "printf '%5d%4d' 1 21 321 4321 54321" \
   "$PRINTF '%5d%4d' 1 21 321 4321 54321" "    1  21  321432154321   0" "" ""
 testing "printf '%c %c' 78 79" "$PRINTF '%c %c' 78 79" "7 7" "" ""
--- a/toys/pending/printf.c	Tue Jan 06 15:06:51 2015 -0600
+++ b/toys/pending/printf.c	Sat Jan 10 20:02:21 2015 -0600
@@ -20,12 +20,6 @@
 #define FOR_printf
 #include "toys.h"
 
-GLOBALS(
-  char *hv_w;
-  char *hv_p;
-  int encountered;
-)
-
 // Detect matching character (return true/valse) and advance pointer if match.
 static int eat(char **s, char c)
 {
@@ -36,78 +30,19 @@
   return x;
 }
 
-// Add ll and L to Interger and floating point formats respectively.
-static char *get_format(char *f)
-{
-  int len = strlen(f);
-  char last = f[--len], *post = "";
-
-  f[len] = 0;
-  if (strchr("diouxX", last)) post = "ll";  // add ll to integer modifier.
-  else if (strchr("feEgG", last)) post = "L"; // add L to float modifier.
-  return xmprintf("%s%s%c", f, post, last);
-}
-
-// Print arguments with corresponding conversion and width and precision.
-static void print(char *fmt, int w, int p, char *arg)
-{
-  char *ptr = fmt, *ep = 0, *format = 0;
-
-  errno = 0;
-  if (strchr("diouxX", *ptr)) {
-    long long val = 0;
-
-    if (arg) {
-      if (*arg == '\'' || *arg == '"') val = arg[1];
-      else {
-        val = strtoll(arg, &ep, 0);
-        if (errno || (ep && (*ep || ep == arg))) {
-          perror_msg("Invalid num %s", arg);
-          val = 0;
-        }
-      }
-    }
-    format = get_format(fmt);
-    TT.hv_w ? (TT.hv_p ? printf(format, w, p, val) : printf(format, w, val))
-      : (TT.hv_p ? printf(format, p, val) : printf(format, val));
-  } else if (strchr("gGeEf", *ptr)) {
-    long double dval = 0;
-
-    if (arg) {
-      dval = strtold(arg, &ep);
-      if (errno || (ep && (*ep || ep == arg))) {
-        perror_msg("Invalid num %s", arg);
-        dval = 0;
-      }
-    }
-    format = get_format(fmt);
-    TT.hv_w ? (TT.hv_p ? printf(format, w, p, dval) : printf(format, w, dval))
-      : (TT.hv_p ? printf(format, p, dval) :  printf(format, dval));
-  } else if (*ptr == 's') {
-    char *str = arg;
-
-    if (!str) str = "";
-
-    TT.hv_w ? (TT.hv_p ? printf(fmt,w,p,str): printf(fmt, w, str))
-      : (TT.hv_p ? printf(fmt, p, str) : printf(fmt, str));
-  } else if (*ptr == 'c') printf(fmt, arg ? *arg : 0);
-
-  if (format) free(format);
-}
-
 // Parse escape sequences.
 static int handle_slash(char **esc_val)
 {
   char *ptr = *esc_val;
-  int len = 1, base = 0;
-  unsigned result = 0;
+  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 (*ptr >= '0' && *ptr <= '8') base = 8;
-  len += (base-8)/8;
+  len = (char []){0,3,2}[base/8];
 
   // Not a hex or octal escape? (This catches trailing \)
   if (!len) {
@@ -118,9 +53,8 @@
   }
 
   while (len) {
-    unsigned num = tolower(*ptr)-'0';
-
-    if (num > 10) num += '0'-'a'+10;
+    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) {
@@ -135,48 +69,76 @@
   }
   *esc_val = ptr;
 
-  return (char)result;
+  return result;
 }
 
 void printf_main(void)
 {
-  char *format = *toys.optargs, **arg = toys.optargs+1, *f, *p;
+  char **arg = toys.optargs+1;
+
+  // Repeat format until arguments consumed
+  for (;;) {
+    int seen = 0;
+    char *f = *toys.optargs;
 
-  for (f = format; *f; f++) {
-    if (eat(&f, '\\')) putchar(handle_slash(&f));
-    else if (!eat(&f, '%') || *f == '%') putchar(*f);
-    else if (*f == 'b')
-      for (p = *arg ? *(arg++) : ""; *p; p++) 
-        putchar(eat(&p, '\\') ? handle_slash(&p) : *p);
-    else {
-      char *start = f;
-      int wp[2], i;
+    // Loop through characters in format
+    while (*f) {
+      if (eat(&f, '\\')) putchar(handle_slash(&f));
+      else if (!eat(&f, '%') || *f == '%') putchar(*f++);
+
+      // Handle %escape
+      else {
+        char c, *start = f, *end = 0, *aa, *width = "";
+        int wp[] = {-1,-1}, i;
+        union {
+          int c; // type promotion
+          char *str;
+          long long ll;
+          double dd;
+        } mash;
 
-      // todo: we currently ignore these?
-      if (strchr("-+# ", *f)) f++;
-      memset(wp, 0, 8);
-      for (i=0; i<2; i++) {
-        if (eat(&f, '*')) {
-          if (*arg) wp[i] = atolx(*(arg++));
-        } else while (isdigit(*f)) f++;
-        if (!eat(&f, '.')) break;
+        // Parse width.precision between % and type indicator.
+        // todo: we currently ignore these?
+        if (strchr("-+# ", *f)) f++;
+        for (i=0; i<2; i++) {
+          if (eat(&f, '*')) {
+            if (*arg) wp[i] = atolx(*arg++);
+          } else while (isdigit(*f)) f++;
+          if (!eat(&f, '.')) break;
+        }
+        seen++;
+        errno = 0;
+        c = *f++;
+        aa = *arg ? *arg++ : "";
+
+        // Handle %esc, assembling new format string into toybuf if necessary.
+        if ((f-start) > sizeof(toybuf)-4) c = 0;
+        if (c == 'b') {
+          while (*aa) putchar(eat(&aa, '\\') ? handle_slash(&aa) : *aa++);
+
+          continue;
+        } else if (c == 'c') mash.c = *aa;
+        else if (c == 's') mash.str = aa;
+        else if (strchr("diouxX", c)) {
+          width = "ll";
+          if (*aa == '\'' || *aa == '"') mash.ll = aa[1];
+          else mash.ll = strtoll(aa, &end, 0);
+        } else if (strchr("feEgG", c)) mash.dd = strtod(aa, &end);
+        else error_exit("bad %%%c@%ld", c, f-*toys.optargs);
+
+        if (end && (errno || *end)) perror_msg("bad %%%c %s", c, aa);
+
+        sprintf(toybuf, "%%%.*s%s%c", (int)(f-start)-1, start, width, c);
+        wp[0]>=0
+          ? (wp[1]>=0 ? printf(toybuf, wp[0], wp[1], mash)
+                      : printf(toybuf, wp[0], mash))
+          : (wp[1]>=0 ? printf(toybuf, wp[1], mash)
+                      : printf(toybuf, mash));
       }
-      if (!(p = strchr("diouxXfeEgGcs", *f)))
-        error_exit("bad format@%ld", f-format);
-      else {
-        int len = f-start;
+    }
 
-        TT.hv_p = strstr(start, ".*");
-        TT.hv_w = strchr(start, '*');
-        //pitfall: handle diff b/w * and .*
-        if ((TT.hv_w-1) == TT.hv_p) TT.hv_w = NULL;
-        memcpy((p = xzalloc(len+1)), start, len);
-        print(p+len-1, wp[0], wp[1], *arg);
-        if (*arg) arg++;
-        free(p);
-        p = NULL;
-      } 
-      TT.encountered = 1;
-    }
+    // 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;
   }
 }