Print this page
4818 printf(1) should support n$ width and precision specifiers

@@ -1,6 +1,7 @@
 /*
+ * Copyright 2014 Garrett D'Amore <garrett@damore.org>
  * Copyright 2010 Nexenta Systems, Inc.  All rights reserved.
  * Copyright (c) 1989, 1993
  *      The Regents of the University of California.  All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without

@@ -36,10 +37,12 @@
 #include <limits.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
+#include <alloca.h>
+#include <ctype.h>
 #include <locale.h>
 #include <note.h>
 
 #define warnx1(a, b, c)         warnx(a)
 #define warnx2(a, b, c)         warnx(a, b)

@@ -49,15 +52,10 @@
 
 #define _(x)    gettext(x)
 
 #define PF(f, func) do {                                                \
         char *b = NULL;                                                 \
-        int dollar = 0;                                                 \
-        if (*f == '$')  {                                               \
-                dollar++;                                               \
-                *f = '%';                                               \
-        }                                                               \
         if (havewidth)                                                  \
                 if (haveprec)                                           \
                         (void) asprintf(&b, f, fieldwidth, precision, func); \
                 else                                                    \
                         (void) asprintf(&b, f, fieldwidth, func);       \

@@ -67,12 +65,10 @@
                 (void) asprintf(&b, f, func);                           \
         if (b) {                                                        \
                 (void) fputs(b, stdout);                                \
                 free(b);                                                \
         }                                                               \
-        if (dollar)                                                     \
-                *f = '$';                                               \
 _NOTE(CONSTCOND) } while (0)
 
 static int       asciicode(void);
 static char     *doformat(char *, int *);
 static int       escape(char *, int, size_t *);

@@ -83,13 +79,16 @@
 static const char
                 *getstr(void);
 static char     *mknum(char *, char);
 static void      usage(void);
 
+static const char digits[] = "0123456789";
+
 static int  myargc;
 static char **myargv;
 static char **gargv;
+static char **maxargv;
 
 int
 main(int argc, char *argv[])
 {
         size_t len;

@@ -128,11 +127,11 @@
         chopped = escape(fmt, 1, &len);         /* backslash interpretation */
         rval = end = 0;
         gargv = ++argv;
 
         for (;;) {
-                char **maxargv = gargv;
+                maxargv = gargv;
 
                 myargv = gargv;
                 for (myargc = 0; gargv[myargc]; myargc++)
                         /* nop */;
                 start = fmt;

@@ -172,68 +171,128 @@
         /* NOTREACHED */
 }
 
 
 static char *
-doformat(char *start, int *rval)
+doformat(char *fmt, int *rval)
 {
         static const char skip1[] = "#'-+ 0";
-        static const char skip2[] = "0123456789";
-        char *fmt;
+        char **save;
         int fieldwidth, haveprec, havewidth, mod_ldbl, precision;
         char convch, nextch;
+        char *start;
+        char *dptr;
+        int l;
 
-        fmt = start + 1;
+        start = alloca(strlen(fmt) + 1);
 
+        dptr = start;
+        *dptr++ = '%';
+        *dptr = 0;
+
+        fmt++;
+
         /* look for "n$" field index specifier */
-        fmt += strspn(fmt, skip2);
-        if ((*fmt == '$') && (fmt != (start + 1))) {
-                int idx = atoi(start + 1);
+        l = strspn(fmt, digits);
+        if ((l > 0) && (fmt[l] == '$')) {
+                int idx = atoi(fmt);
                 if (idx <= myargc) {
                         gargv = &myargv[idx - 1];
                 } else {
                         gargv = &myargv[myargc];
                 }
-                start = fmt;
-                fmt++;
-        } else {
-                fmt = start + 1;
+                if (gargv > maxargv) {
+                        maxargv = gargv;
         }
+                fmt += l + 1;
+        }
 
+        save = gargv;
+
         /* skip to field width */
-        fmt += strspn(fmt, skip1);
+        while (strchr(skip1, *fmt) != NULL) {
+                *dptr++ = *fmt++;
+                *dptr = 0;
+        }
+
         if (*fmt == '*') {
+
+                fmt++;
+                l = strspn(fmt, digits);
+                if ((l > 0) && (fmt[l] == '$')) {
+                        int idx = atoi(fmt);
+                        if (idx <= myargc) {
+                                gargv = &myargv[idx - 1];
+                        } else {
+                                gargv = &myargv[myargc];
+                        }
+                        fmt += l + 1;
+                }
+
                 if (getint(&fieldwidth))
                         return (NULL);
+                if (gargv > maxargv) {
+                        maxargv = gargv;
+                }
                 havewidth = 1;
-                ++fmt;
+
+                *dptr++ = '*';
+                *dptr = 0;
         } else {
                 havewidth = 0;
 
                 /* skip to possible '.', get following precision */
-                fmt += strspn(fmt, skip2);
+                while (isdigit(*fmt)) {
+                        *dptr++ = *fmt++;
+                        *dptr = 0;
         }
+        }
+
         if (*fmt == '.') {
                 /* precision present? */
-                ++fmt;
+                fmt++;
+                *dptr++ = '.';
+
                 if (*fmt == '*') {
+
+                        fmt++;
+                        l = strspn(fmt, digits);
+                        if ((l > 0) && (fmt[l] == '$')) {
+                                int idx = atoi(fmt);
+                                if (idx <= myargc) {
+                                        gargv = &myargv[idx - 1];
+                                } else {
+                                        gargv = &myargv[myargc];
+                                }
+                                fmt += l + 1;
+                        }
+
                         if (getint(&precision))
                                 return (NULL);
+                        if (gargv > maxargv) {
+                                maxargv = gargv;
+                        }
                         haveprec = 1;
-                        ++fmt;
+                        *dptr++ = '*';
+                        *dptr = 0;
                 } else {
                         haveprec = 0;
 
                         /* skip to conversion char */
-                        fmt += strspn(fmt, skip2);
+                        while (isdigit(*fmt)) {
+                                *dptr++ = *fmt++;
+                                *dptr = 0;
                 }
+                }
         } else
                 haveprec = 0;
         if (!*fmt) {
                 warnx1(_("missing format character"), NULL, NULL);
                 return (NULL);
         }
+        *dptr++ = *fmt;
+        *dptr = 0;
 
         /*
          * Look for a length modifier.  POSIX doesn't have these, so
          * we only support them for floating-point conversions, which
          * are extensions.  This is useful because the L modifier can

@@ -252,12 +311,14 @@
                 }
         } else {
                 mod_ldbl = 0;
         }
 
+        gargv = save;
         convch = *fmt;
         nextch = *++fmt;
+
         *fmt = '\0';
         switch (convch) {
         case 'b': {
                 size_t len;
                 char *p;