gawk-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[gawk-diffs] [SCM] gawk branch, num-handler, updated. 390353f51f36ca5351


From: John Haque
Subject: [gawk-diffs] [SCM] gawk branch, num-handler, updated. 390353f51f36ca53515630d38b63d6bbb1c4f43d
Date: Thu, 03 Jan 2013 10:50:21 +0000

This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "gawk".

The branch, num-handler has been updated
       via  390353f51f36ca53515630d38b63d6bbb1c4f43d (commit)
      from  7c017f175f74131de421563b127f9a554cc8fa07 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
http://git.sv.gnu.org/cgit/gawk.git/commit/?id=390353f51f36ca53515630d38b63d6bbb1c4f43d

commit 390353f51f36ca53515630d38b63d6bbb1c4f43d
Author: John Haque <address@hidden>
Date:   Wed Jan 2 18:58:01 2013 -0600

    Refactoring format_tree() routine.

diff --git a/ChangeLog b/ChangeLog
index 01829c7..389c61f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,25 @@
+2013-01-03         John Haque      <address@hidden>
+
+       Refactor format_tree() to seperate number formatting code.
+
+       * format.h: New file.
+       (format_spec, print_fmt_buf): Definitions.
+       (chksize, bchunk, bchunk_one, buf_adjust, buf2node,
+       tmpbuf_prepend, pr_fill, pr_num_tail, free_print_fmt_buf):
+       Inline routines.
+       * awk.h (num_handler_t): New fields gawk_format_printf,
+       gawk_isnan, gawk_isinf. Removed field gawk_format_nodes.
+       * builtin.c (chksize__internal, cpbuf_chksize__internal,
+       get_fmt_buf, format_nondecimal): New routines.
+       (format_tree): Restore function format_tree(). Adjusted to call
+       the current number formatting routine.
+       * double.c (awknum_isnan, awknum_isinf, format_awknum_printf):
+       New routines.
+       (format_nodes_awknum): Removed.
+       * mpfr.c (mpfp_isnan, mpfp_isinf, mpfp_format_printf):
+       New routines.
+       (mpfp_format_nodes): Removed.
+
 2012-12-28         John Haque      <address@hidden>
 
        * double.c: Use make_awknum everywhere instead of make_number
diff --git a/awk.h b/awk.h
index 89f2682..e459a95 100644
--- a/awk.h
+++ b/awk.h
@@ -868,6 +868,8 @@ typedef struct {
        NODE *(*ptr)(int);      /* function that implements this built-in */
 } bltin_t;
 
+struct format_spec;
+struct print_fmt_buf;
 
 typedef struct {
        bool (*init)(bltin_t **);                /* initialization */
@@ -884,17 +886,20 @@ typedef struct {
        NODE *(*gawk_force_number)(NODE *);      /* force a NODE value to be 
numeric */
        void (*gawk_negate_number)(NODE *);      /* in place negation of a 
number */
        int (*gawk_cmp_numbers)(const NODE *, const NODE *);   /* compare two 
numbers */
+
        int (*gawk_sgn_number)(const NODE *);    /* test if a numeric node is 
zero,
                                                     positive or negative */
-       bool (*gawk_is_integer)(const NODE *);    /* test if a number is an 
integer */
+       bool (*gawk_isinteger)(const NODE *);    /* test if a number is an 
integer */
+       bool (*gawk_isnan)(const NODE *);        /* test if NaN */
+       bool (*gawk_isinf)(const NODE *);        /* test if infinity */
 
        NODE *(*gawk_fmt_number)(const char *, int, NODE *);   /* stringify a 
numeric value
                                                                  based on awk 
input/output format */
-       NODE *(*gawk_format_nodes)(const char *, size_t, NODE **, long); /* 
format NODES according to
-                                                                           
user-specified FORMAT */  
+
+       int (*gawk_format_printf)(NODE *, struct format_spec *, struct 
print_fmt_buf *); /* (s)printf format */
 
        /* conversion to C types */
-       AWKNUM (*gawk_todouble)(const NODE *);         /* number to AWKNUM */
+       double (*gawk_todouble)(const NODE *);         /* number to double */
        long (*gawk_tolong)(const NODE *);             /* number to long */
        unsigned long (*gawk_toulong)(const NODE *);   /* number to unsigned 
long */
        uintmax_t (*gawk_touintmax_t)(const NODE *);   /* number to uintmax_t */
@@ -1104,7 +1109,8 @@ extern long (*get_number_si)(const NODE *);
 extern double (*get_number_d)(const NODE *);
 extern uintmax_t (*get_number_uj)(const NODE *);
 extern int (*sgn_number)(const NODE *);
-extern NODE *(*format_tree)(const char *, size_t, NODE **, long);
+extern int (*format_number_printf)(NODE *, struct format_spec *, struct 
print_fmt_buf *);
+
 
 /* built-in array types */
 extern afunc_t str_array_func[];
@@ -1234,7 +1240,7 @@ DEREF(NODE *r)
 
 /* ------------------------- Pseudo-functions ------------------------- */
 #define iszero(n)      (sgn_number(n) == 0)
-#define isinteger(n)   numbr_hndlr->gawk_is_integer(n)
+#define isinteger(n)   numbr_hndlr->gawk_isinteger(n)
 
 #define var_uninitialized(n)   ((n)->var_value == Nnull_string)
 
@@ -1401,6 +1407,7 @@ extern NODE *do_bindtextdomain(int nargs);
 extern int strncasecmpmbs(const unsigned char *,
                          const unsigned char *, size_t);
 #endif
+extern NODE *format_tree(const char *, size_t, NODE **, long);
 
 /* eval.c */
 extern void PUSH_CODE(INSTRUCTION *cp);
diff --git a/builtin.c b/builtin.c
index f6c80d3..720a12c 100644
--- a/builtin.c
+++ b/builtin.c
@@ -2061,3 +2061,640 @@ mbc_char_count(const char *ptr, size_t numbytes)
        return numbytes;
 #endif
 }
+
+
+#include "format.h"
+
+char lchbuf[] = "0123456789abcdef";
+char Uchbuf[] = "0123456789ABCDEF";
+char space_string[] = " ";
+char zero_string[] = "0";
+
+
+#define OUTBUF_INIT_SIZE       510
+
+/* chksize__internal --- make room for something LEN big in the output buffer 
*/
+
+static void
+chksize__internal(struct print_fmt_buf *outb, size_t len)
+{
+       size_t delta, newsize;
+
+       assert(outb->buf != NULL);
+       delta = outb->dataend - outb->buf;
+       newsize = delta + len + OUTBUF_INIT_SIZE;
+       erealloc(outb->buf, char *, newsize + 2, "chksize__internal");
+       outb->dataend = outb->buf + delta;
+       outb->bufsize = newsize;
+       outb->room_left = len + OUTBUF_INIT_SIZE;
+}
+
+/* cpbuf_chksize__internal --- enlarge the temporary buffer */
+
+static void
+cpbuf_chksize__internal(struct print_fmt_buf *outb)
+{
+       char *cp, *prev = outb->cpbuf.buf;
+       size_t oldsize = outb->cpbuf.bufsize;
+       size_t newsize;
+
+       newsize = outb->cpbuf.bufsize = 2 * oldsize;
+       emalloc(outb->cpbuf.buf, char *, newsize + 2, 
"cpbuf_chksize__internal");
+       memcpy((cp = outb->cpbuf.buf + oldsize), prev,  oldsize);
+       efree(prev);
+       outb->cpbuf.bufend = outb->cpbuf.buf + newsize;
+       outb->cpbuf.databegin = cp;
+}
+
+static struct print_fmt_buf static_outb;
+
+/* get_fmt_buf --- buffer(s) to manage (s)printf formatting */
+ 
+struct print_fmt_buf *
+get_fmt_buf()
+{
+       struct print_fmt_buf *outb;
+
+       if (static_outb.buf == NULL)
+               outb = & static_outb;
+       else {
+               emalloc(outb, struct print_fmt_buf *, sizeof (struct 
print_fmt_buf), "get_fmt_buf");
+               outb->is_malloced = true;
+       }
+
+       emalloc(outb->buf, char *, OUTBUF_INIT_SIZE + 2, "get_fmt_buf");
+       outb->bufsize = OUTBUF_INIT_SIZE;
+       outb->room_left = outb->bufsize;
+       outb->dataend = outb->buf;
+       outb->chksize = chksize__internal;
+
+       emalloc(outb->cpbuf.buf, char *, 64, "get_fmt_buf");
+       outb->cpbuf.bufsize = 62;
+       outb->cpbuf.databegin = outb->cpbuf.bufend = outb->cpbuf.buf + 
outb->cpbuf.bufsize;
+       outb->cpbuf_chksize = cpbuf_chksize__internal;
+       return outb;
+}
+
+
+#      define CP               cpbuf_start(outb)
+#      define CEND             cpbuf_end(outb)
+#      define CPBUF            cpbuf(outb)
+
+
+/* format_nondecimal --- output a nondecimal number according to a format */
+
+void
+format_nondecimal(uintmax_t val, struct format_spec *spec, struct 
print_fmt_buf *outb)
+{
+       uintmax_t uval = val;
+       int ii, jj;
+       const char *chbuf = spec->chbuf;
+
+       /*
+        * When to fill with zeroes is of course not simple.
+        * First: No zero fill if left-justifying.
+        * Next: There seem to be two cases:
+        *      A '0' without a precision, e.g. %06d
+        *      A precision with no field width, e.g. %.10d
+        * Any other case, we don't want to fill with zeroes.
+        */
+       if (! spec->lj
+                   && ((spec->zero_flag && ! spec->have_prec)
+                       || (spec->fw == 0 && spec->have_prec))
+       )
+               spec->fill = zero_string;
+
+       ii = jj = 0;
+       do {
+               tmpbuf_prepend(outb, chbuf[uval % spec->base]);
+               uval /= spec->base;
+#if defined(HAVE_LOCALE_H)
+               if (spec->base == 10 && spec->quote_flag && loc.grouping[ii] && 
++jj == loc.grouping[ii]) {
+                       if (uval)       /* only add if more digits coming */
+                               tmpbuf_prepend(outb, loc.thousands_sep[0]);     
/* XXX --- assumption it's one char */
+                       if (loc.grouping[ii+1] == 0)                            
              
+                               jj = 0;     /* keep using current val in 
loc.grouping[ii] */
+                       else if (loc.grouping[ii+1] == CHAR_MAX)                
        
+                               spec->quote_flag= false;
+                       else {                 
+                               ii++;
+                               jj = 0;
+                       }
+               }
+#endif
+       } while (uval > 0);
+
+       /* add more output digits to match the precision */
+       if (spec->have_prec) {
+               while (CEND - CP < spec->prec)
+                       tmpbuf_prepend(outb, '0');
+       }
+
+       if (spec->alt && val != 0) {
+               if (spec->base == 16) {
+                       tmpbuf_prepend(outb, spec->fmtchar);
+                       tmpbuf_prepend(outb, '0');
+                       if (spec->fill != space_string) {
+                               bchunk(outb, CP, 2);
+                               CP += 2;
+                               spec->fw -= 2;
+                       }
+               } else if (spec->base == 8)
+                       tmpbuf_prepend(outb, '0');
+       }
+
+       spec->base = 0;
+       if (spec->prec > spec->fw)
+               spec->fw = spec->prec;
+       spec->prec = CEND - CP;
+       pr_num_tail(CP, spec->prec, spec, outb);
+}
+
+
+/*
+ * format_tree() formats arguments of sprintf,
+ * and accordingly to a fmt_string providing a format like in
+ * printf family from C library.  Returns a string node which value
+ * is a formatted string.  Called by  sprintf function.
+ *
+ * It is one of the uglier parts of gawk.  Thanks to Michal Jaegermann
+ * for taming this beast and making it compatible with ANSI C.
+ */
+
+
+NODE *
+format_tree(
+       const char *fmt_string,
+       size_t n0,
+       NODE **the_args,
+       long num_args)
+{
+       size_t cur_arg = 0;
+       NODE *r = NULL;
+       bool toofew = false;
+       const char *s0, *s1;
+       int cs1;
+       NODE *arg;
+       long argnum;
+
+       bool used_dollar;
+       bool big_flag, bigbig_flag, small_flag, need_format;
+       long *cur = NULL;
+       uintmax_t uval;
+       char *cp;
+       size_t copy_count, char_count;
+
+       struct print_fmt_buf *outb;
+       struct format_spec spec;
+
+
+       /*
+        * Check first for use of `count$'.
+        * If plain argument retrieval was used earlier, choke.
+        *      Otherwise, return the requested argument.
+        * If not `count$' now, but it was used earlier, choke.
+        * If this format is more than total number of args, choke.
+        * Otherwise, return the current argument.
+        */
+#define parse_next_arg() { \
+       if (argnum > 0) { \
+               if (cur_arg > 1) { \
+                       msg(_("fatal: must use `count$' on all formats or 
none")); \
+                       goto out; \
+               } \
+               arg = the_args[argnum]; \
+       } else if (used_dollar) { \
+               msg(_("fatal: must use `count$' on all formats or none")); \
+               arg = 0; /* shutup the compiler */ \
+               goto out; \
+       } else if (cur_arg >= num_args) { \
+               arg = 0; /* shutup the compiler */ \
+               toofew = true; \
+               break; \
+       } else { \
+               arg = the_args[cur_arg]; \
+               cur_arg++; \
+       } \
+}
+
+       outb = get_fmt_buf();
+       cur_arg = 1;
+
+       need_format = false;
+       used_dollar = false;
+
+       s0 = s1 = fmt_string;
+
+       while (n0-- > 0) {
+               if (*s1 != '%') {
+                       s1++;
+                       continue;
+               }
+               need_format = true;
+               bchunk(outb, s0, s1 - s0);
+               s0 = s1;
+               argnum = 0;
+
+               memset(& spec, '\0', sizeof (spec));
+               cur = & spec.fw;
+               spec.fill = space_string;       /* always space for string */
+
+               big_flag = bigbig_flag = small_flag = false;
+               CP = CEND;
+               s1++;
+
+retry:
+               if (n0-- == 0)  /* ran out early! */
+                       break;
+
+               switch (cs1 = *s1++) {
+               case (-1):      /* dummy case to allow for checking */
+check_pos:
+                       if (cur != & spec.fw)
+                               break;          /* reject as a valid format */
+                       goto retry;
+               case '%':
+                       need_format = false;
+                       /*
+                        * 29 Oct. 2002:
+                        * The C99 standard pages 274 and 279 seem to imply that
+                        * since there's no arg converted, the field width 
doesn't
+                        * apply.  The code already was that way, but this
+                        * comment documents it, at least in the code.
+                        */
+                       if (do_lint) {
+                               const char *msg = NULL;
+
+                               if (spec.fw && ! spec.have_prec)
+                                       msg = _("field width is ignored for 
`%%' specifier");
+                               else if (spec.fw == 0 && spec.have_prec)
+                                       msg = _("precision is ignored for `%%' 
specifier");
+                               else if (spec.fw && spec.have_prec)
+                                       msg = _("field width and precision are 
ignored for `%%' specifier");
+
+                               if (msg != NULL)
+                                       lintwarn("%s", msg);
+                       }
+                       bchunk_one(outb, "%");
+                       s0 = s1;
+                       break;
+
+               case '0':
+                       /*
+                        * Only turn on zero_flag if we haven't seen
+                        * the field width or precision yet.  Otherwise,
+                        * screws up floating point formatting.
+                        */
+                       if (cur == & spec.fw)
+                               spec.zero_flag = true;
+                       if (spec.lj)
+                               goto retry;
+                       /* FALL through */
+               case '1':
+               case '2':
+               case '3':
+               case '4':
+               case '5':
+               case '6':
+               case '7':
+               case '8':
+               case '9':
+                       if (cur == NULL)
+                               break;
+                       if (spec.prec >= 0)
+                               *cur = cs1 - '0';
+                       /*
+                        * with a negative precision *cur is already set
+                        * to -1, so it will remain negative, but we have
+                        * to "eat" precision digits in any case
+                        */
+                       while (n0 > 0 && *s1 >= '0' && *s1 <= '9') {
+                               --n0;
+                               *cur = *cur * 10 + *s1++ - '0';
+                       }
+                       if (spec.prec < 0)      /* negative precision is 
discarded */
+                               spec.have_prec = false;
+                       if (cur == & spec.prec)
+                               cur = NULL;
+                       if (n0 == 0)    /* badly formatted control string */
+                               continue;
+                       goto retry;
+               case '$':
+                       if (do_traditional) {
+                               msg(_("fatal: `$' is not permitted in awk 
formats"));
+                               goto out;
+                       }
+
+                       if (cur == & spec.fw) {
+                               argnum = spec.fw;
+                               spec.fw = 0;
+                               used_dollar = true;
+                               if (argnum <= 0) {
+                                       msg(_("fatal: arg count with `$' must 
be > 0"));
+                                       goto out;
+                               }
+                               if (argnum >= num_args) {
+                                       msg(_("fatal: arg count %ld greater 
than total number of supplied arguments"), argnum);
+                                       goto out;
+                               }
+                       } else {
+                               msg(_("fatal: `$' not permitted after period in 
format"));
+                               goto out;
+                       }
+
+                       goto retry;
+               case '*':
+                       if (cur == NULL)
+                               break;
+                       if (! do_traditional && isdigit((unsigned char) *s1)) {
+                               int val = 0;
+
+                               for (; n0 > 0 && *s1 && isdigit((unsigned char) 
*s1); s1++, n0--) {
+                                       val *= 10;
+                                       val += *s1 - '0';
+                               }
+                               if (*s1 != '$') {
+                                       msg(_("fatal: no `$' supplied for 
positional field width or precision"));
+                                       goto out;
+                               } else {
+                                       s1++;
+                                       n0--;
+                               }
+                               if (val >= num_args) {
+                                       toofew = true;
+                                       break;
+                               }
+                               arg = the_args[val];
+                       } else {
+                               parse_next_arg();
+                       }
+
+                       (void) force_number(arg);
+                       *cur = get_number_si(arg);
+                       if (*cur < 0 && cur == & spec.fw) {
+                               *cur = -*cur;
+                               spec.lj++;
+                       }
+                       if (cur == & spec.prec) {
+                               if (*cur >= 0)
+                                       spec.have_prec = true;
+                               else
+                                       spec.have_prec = false;
+                               cur = NULL;
+                       }
+                       goto retry;
+               case ' ':               /* print ' ' or '-' */
+                                       /* 'space' flag is ignored */
+                                       /* if '+' already present  */
+                       if (spec.signchar != false) 
+                               goto check_pos;
+                       /* FALL THROUGH */
+               case '+':               /* print '+' or '-' */
+                       spec.signchar = cs1;
+                       goto check_pos;
+               case '-':
+                       if (spec.prec < 0)
+                               break;
+                       if (cur == & spec.prec) {
+                               spec.prec = -1;
+                               goto retry;
+                       }
+                       spec.lj++;              /* filling is ignored */
+                       goto check_pos;
+               case '.':
+                       if (cur != & spec.fw)
+                               break;
+                       cur = & spec.prec;
+                       spec.have_prec = true;
+                       goto retry;
+               case '#':
+                       spec.alt = true;
+                       goto check_pos;
+               case '\'':
+#if defined(HAVE_LOCALE_H)       
+                       /* allow quote_flag if there is a thousands separator. 
*/
+                       if (loc.thousands_sep[0] != '\0')
+                               spec.quote_flag= true;
+                       goto check_pos;
+#else
+                       goto retry;  
+#endif
+               case 'l':
+                       if (big_flag)
+                               break;
+                       else {
+                               static bool warned = false;
+                               
+                               if (do_lint && ! warned) {
+                                       lintwarn(_("`l' is meaningless in awk 
formats; ignored"));
+                                       warned = true;
+                               }
+                               if (do_posix) {
+                                       msg(_("fatal: `l' is not permitted in 
POSIX awk formats"));
+                                       goto out;
+                               }
+                       }
+                       big_flag = true;
+                       goto retry;
+               case 'L':
+                       if (bigbig_flag)
+                               break;
+                       else {
+                               static bool warned = false;
+                               
+                               if (do_lint && ! warned) {
+                                       lintwarn(_("`L' is meaningless in awk 
formats; ignored"));
+                                       warned = true;
+                               }
+                               if (do_posix) {
+                                       msg(_("fatal: `L' is not permitted in 
POSIX awk formats"));
+                                       goto out;
+                               }
+                       }
+                       bigbig_flag = true;
+                       goto retry;
+               case 'h':
+                       if (small_flag)
+                               break;
+                       else {
+                               static bool warned = false;
+                               
+                               if (do_lint && ! warned) {
+                                       lintwarn(_("`h' is meaningless in awk 
formats; ignored"));
+                                       warned = true;
+                               }
+                               if (do_posix) {
+                                       msg(_("fatal: `h' is not permitted in 
POSIX awk formats"));
+                                       goto out;
+                               }
+                       }
+                       small_flag = true;
+                       goto retry;
+               case 'c':
+                       need_format = false;
+                       parse_next_arg();
+                       /* user input that looks numeric is numeric */
+                       if ((arg->flags & (MAYBE_NUM|NUMBER)) == MAYBE_NUM)
+                               (void) force_number(arg);
+                       if ((arg->flags & NUMBER) != 0) {
+                               uval = get_number_uj(arg);
+#if MBS_SUPPORT
+                               if (gawk_mb_cur_max > 1) {
+                                       char buf[100];
+                                       wchar_t wc;
+                                       mbstate_t mbs;
+                                       size_t count;
+
+                                       memset(& mbs, 0, sizeof(mbs));
+                                       wc = uval;
+
+                                       count = wcrtomb(buf, wc, & mbs);
+                                       if (count == 0
+                                           || count == (size_t)-1
+                                           || count == (size_t)-2)
+                                               goto out0;
+
+                                       memcpy(CPBUF, buf, count);
+                                       spec.prec = count;
+                                       cp = CPBUF;
+                                       goto pr_tail;
+                               }
+out0:
+                               ;
+                               /* else,
+                                       fall through */
+#endif
+                               if (do_lint && uval > 255) {
+                                       lintwarn("[s]printf: value %g is too 
big for %%c format",
+                                                       get_number_d(arg));
+                               }
+                               CPBUF[0] = uval;
+                               spec.prec = 1;
+                               cp = CPBUF;
+                               goto pr_tail;
+                       }
+                       /*
+                        * As per POSIX, only output first character of a
+                        * string value.  Thus, we ignore any provided
+                        * precision, forcing it to 1.  (Didn't this
+                        * used to work? 6/2003.)
+                        */
+                       cp = arg->stptr;
+#if MBS_SUPPORT
+                       /*
+                        * First character can be multiple bytes if
+                        * it's a multibyte character. Grr.
+                        */
+                       if (gawk_mb_cur_max > 1) {
+                               mbstate_t state;
+                               size_t count;
+
+                               memset(& state, 0, sizeof(state));
+                               count = mbrlen(cp, arg->stlen, & state);
+                               if (count == 0
+                                   || count == (size_t)-1
+                                   || count == (size_t)-2)
+                                       goto out2;
+                               spec.prec = count;
+                               goto pr_tail;
+                       }
+out2:
+                       ;
+#endif
+                       spec.prec = 1;
+                       goto pr_tail;
+               case 's':
+                       need_format = false;
+                       parse_next_arg();
+                       arg = force_string(arg);
+                       if (spec.fw == 0 && ! spec.have_prec)
+                               spec.prec = arg->stlen;
+                       else {
+                               char_count = mbc_char_count(arg->stptr, 
arg->stlen);
+                               if (! spec.have_prec || spec.prec > char_count)
+                                       spec.prec = char_count;
+                       }
+                       cp = arg->stptr;
+       pr_tail:
+                       if (! spec.lj)
+                               pr_fill(& spec, outb);
+
+                       copy_count = spec.prec;
+                       if (spec.fw == 0 && ! spec.have_prec)
+                               ;
+                       else if (gawk_mb_cur_max > 1 && (cs1 == 's' || cs1 == 
'c')) {
+                               assert(cp == arg->stptr || cp == CPBUF);
+                               copy_count = mbc_byte_count(arg->stptr, 
spec.prec);
+                       }
+
+                       bchunk(outb, cp, copy_count);
+                       pr_fill(& spec, outb);
+
+                       s0 = s1;
+                       break;
+
+               case 'X':
+                       /* FALL THROUGH */
+               case 'x':
+                       spec.base += 6;
+                       /* FALL THROUGH */
+               case 'u':
+                       spec.base += 2;
+                       /* FALL THROUGH */
+               case 'o':
+                       spec.base += 8;
+                       goto fmt1;
+               case 'F':
+#if ! defined(PRINTF_HAS_F_FORMAT) || PRINTF_HAS_F_FORMAT != 1
+                       cs1 = 'f';
+                       /* FALL THROUGH */
+#endif
+               case 'g':
+               case 'G':
+               case 'e':
+               case 'f':
+               case 'E':
+               case 'd':
+               case 'i':
+       fmt1:
+                       need_format = false;
+                       parse_next_arg();
+                       (void) force_number(arg);
+                       spec.fmtchar = cs1;
+                       if (format_number_printf(arg, & spec, outb) < 0)
+                               goto out;
+
+                       s0 = s1;
+                       break;
+
+               default:
+                       if (do_lint && isalpha(cs1))
+       lintwarn(_("ignoring unknown format specifier character `%c': no 
argument converted"), cs1);
+                       break;
+               }
+               if (toofew) {
+                       msg("%s\n\t`%s'\n\t%*s%s",
+                             _("fatal: not enough arguments to satisfy format 
string"),
+                             fmt_string, (int) (s1 - fmt_string - 1), "",
+                             _("^ ran out for this one"));
+                       goto out;
+               }
+       }
+
+       if (do_lint) {
+               if (need_format)
+                       lintwarn(_("[s]printf: format specifier does not have 
control letter"));
+               if (cur_arg < num_args)
+                       lintwarn(_("too many arguments supplied for format 
string"));
+       }
+
+       bchunk(outb, s0, s1 - s0);
+       r = buf2node(outb);
+out:
+       free_fmt_buf(outb);
+
+       if (r == NULL)
+               gawk_exit(EXIT_FATAL);
+       return r;
+}
+
diff --git a/double.c b/double.c
index 985d4f4..b5b7458 100644
--- a/double.c
+++ b/double.c
@@ -28,6 +28,8 @@
 #include "random.h"
 #include "floatmagic.h"        /* definition of isnan */
 
+#include "format.h"
+
 /* Can declare these, since we always use the random shipped with gawk */
 extern char *initstate(unsigned long seed, char *state, long n);
 extern char *setstate(char *state);
@@ -52,12 +54,14 @@ static NODE *force_awknum(NODE *);
 static NODE *format_awknum_val(const char *, int, NODE *);
 static unsigned long awknum_toulong(const NODE *);
 static long awknum_tolong(const NODE *);
-static AWKNUM awknum_todouble(const NODE *);
+static double awknum_todouble(const NODE *);
 static uintmax_t awknum_touintmax_t(const NODE *);
 static int awknum_sgn(const NODE *);
-static bool awknum_is_integer(const NODE *);
+static bool awknum_isinteger(const NODE *);
+static bool awknum_isnan(const NODE *);
+static bool awknum_isinf(const NODE *);
 static NODE *awknum_copy(const NODE *);
-static NODE *format_nodes_awknum(const char *, size_t, NODE **, long);
+static int format_awknum_printf(NODE *, struct format_spec *, struct 
print_fmt_buf *);
 static bool awknum_init(bltin_t **);
 static NODE *awknum_add(const NODE *, const NODE *);
 static NODE *awknum_sub(const NODE *, const NODE *);
@@ -107,9 +111,11 @@ numbr_handler_t awknum_hndlr = {
        negate_awknum,
        cmp_awknums,
        awknum_sgn,
-       awknum_is_integer,
+       awknum_isinteger,
+       awknum_isnan,
+       awknum_isinf,
        format_awknum_val,
-       format_nodes_awknum,
+       format_awknum_printf,
        awknum_todouble,
        awknum_tolong,
        awknum_toulong,
@@ -184,7 +190,7 @@ awknum_tolong(const NODE *n)
 
 /* awknum_todouble --- conversion to AWKNUM */
 
-static AWKNUM
+static double
 awknum_todouble(const NODE *n)
 {
        return n->numbr;
@@ -206,16 +212,33 @@ awknum_sgn(const NODE *n)
        return (n->numbr < 0.0 ? -1 : n->numbr > 0.0);
 }
 
-/* awknum_is_integer --- check if a number is an integer */
+/* awknum_isinteger --- check if a number is an integer */
 
 static bool
-awknum_is_integer(const NODE *n)
+awknum_isinteger(const NODE *n)
 {
        if (isnan(n->numbr) || isinf(n->numbr))
                return false;
        return double_to_int(n->numbr) == n->numbr;
 }
 
+/* awknum_isnan --- check if number is NaN */
+
+static bool
+awknum_isnan(const NODE *n)
+{
+       return isnan(n->numbr);
+}
+
+/* awknum_isinf --- check if number is infinity */
+
+static bool
+awknum_isinf(const NODE *n)
+{
+       return isinf(n->numbr);
+}
+
+
 /* negate_awknum --- negate AWKNUM in NODE */
 
 static void
@@ -1256,835 +1279,232 @@ do_strtonum(int nargs)
        return make_awknum(d);
 }
 
+/* format_awknum_printf --- format a number for (s)printf */
 
-/*
- * format_tree() formats arguments of sprintf,
- * and accordingly to a fmt_string providing a format like in
- * printf family from C library.  Returns a string node which value
- * is a formatted string.  Called by  sprintf function.
- *
- * It is one of the uglier parts of gawk.  Thanks to Michal Jaegermann
- * for taming this beast and making it compatible with ANSI C.
- */
-
-static NODE *
-format_nodes_awknum(
-       const char *fmt_string,
-       size_t n0,
-       NODE **the_args,
-       long num_args)
+static int
+format_awknum_printf(NODE *arg, struct format_spec *spec, struct print_fmt_buf 
*outb)
 {
-/* copy 'l' bytes from 's' to 'obufout' checking for space in the process */
-/* difference of pointers should be of ptrdiff_t type, but let us be kind */
-#define bchunk(s, l) if (l) { \
-       while ((l) > ofre) { \
-               size_t olen = obufout - obuf; \
-               erealloc(obuf, char *, osiz * 2, "format_tree"); \
-               ofre += osiz; \
-               osiz *= 2; \
-               obufout = obuf + olen; \
-       } \
-       memcpy(obufout, s, (size_t) (l)); \
-       obufout += (l); \
-       ofre -= (l); \
-}
+       AWKNUM tmpval;
+       uintmax_t uval;
+       bool sgn;
+       int i, ii, jj;
+       char *chp, *cp;
+       char cs1;
+       int nc;
+
+       static char stackbuf[64];       /* temporary buffer for integer 
formatting */ 
+       static char *intbuf = stackbuf;
+       size_t intbuf_size = 64;
+
+#      define CP               cpbuf_start(outb)
+#      define CEND             cpbuf_end(outb)
+#      define CPBUF            cpbuf(outb)
+
+
+       tmpval = arg->numbr;
+       spec->fill = space_string;
+       spec->chbuf = lchbuf;
+
+       cp = CP;
+       cs1 = spec->fmtchar;
+       switch (cs1) {
+       case 'd':
+       case 'i':
+               if (isnan(tmpval) || isinf(tmpval))
+                       goto out_of_range;
+               else
+                       tmpval = double_to_int(tmpval);
 
-/* copy one byte from 's' to 'obufout' checking for space in the process */
-#define bchunk_one(s) { \
-       if (ofre < 1) { \
-               size_t olen = obufout - obuf; \
-               erealloc(obuf, char *, osiz * 2, "format_tree"); \
-               ofre += osiz; \
-               osiz *= 2; \
-               obufout = obuf + olen; \
-       } \
-       *obufout++ = *s; \
-       --ofre; \
-}
+               /*
+                * ``The result of converting a zero value with a
+                * precision of zero is no characters.''
+                */
+               if (spec->have_prec && spec->prec == 0 && tmpval == 0) {
+                       pr_num_tail(cp, spec->prec, spec, outb);
+                       return 0;
+               }
 
-/* Is there space for something L big in the buffer? */
-#define chksize(l)  if ((l) >= ofre) { \
-       size_t olen = obufout - obuf; \
-       size_t delta = osiz+l-ofre; \
-       erealloc(obuf, char *, osiz + delta, "format_tree"); \
-       obufout = obuf + olen; \
-       ofre += delta; \
-       osiz += delta; \
-}
+               if (tmpval < 0) {
+                       tmpval = -tmpval;
+                       sgn = true;
+               } else {
+                       if (tmpval == -0.0)     /* avoid printing -0 */
+                               tmpval = 0.0;
+                       sgn = false;
+               }
 
-       size_t cur_arg = 0;
-       NODE *r = NULL;
-       int i, nc;
-       bool toofew = false;
-       char *obuf, *obufout;
-       size_t osiz, ofre;
-       const char *chbuf;
-       const char *s0, *s1;
-       int cs1;
-       NODE *arg;
-       long fw, prec, argnum;
-       bool used_dollar;
-       bool lj, alt, big_flag, bigbig_flag, small_flag, have_prec, need_format;
-       long *cur = NULL;
-       uintmax_t uval;
-       bool sgn;
-       int base;
-       /*
-        * Although this is an array, the elements serve two different
-        * purposes. The first element is the general buffer meant
-        * to hold the entire result string.  The second one is a
-        * temporary buffer for large floating point values. They
-        * could just as easily be separate variables, and the
-        * code might arguably be clearer.
-        */
-       struct {
-               char *buf;
-               size_t bufsize;
-               char stackbuf[30];
-       } cpbufs[2];
-#define cpbuf  cpbufs[0].buf
-       char *cend = &cpbufs[0].stackbuf[sizeof(cpbufs[0].stackbuf)];
-       char *cp;
-       const char *fill;
-       AWKNUM tmpval = 0.0;
-       char signchar = '\0';
-       size_t len;
-       bool zero_flag = false;
-       bool quote_flag = false;
-       int ii, jj;
-       char *chp;
-       size_t copy_count, char_count;
-
-       static const char sp[] = " ";
-       static const char zero_string[] = "0";
-       static const char lchbuf[] = "0123456789abcdef";
-       static const char Uchbuf[] = "0123456789ABCDEF";
-
-#define INITIAL_OUT_SIZE       512
-       emalloc(obuf, char *, INITIAL_OUT_SIZE, "format_tree");
-       obufout = obuf;
-       osiz = INITIAL_OUT_SIZE;
-       ofre = osiz - 2;
-
-       cur_arg = 1;
-
-       {
-               size_t k;
-               for (k = 0; k < sizeof(cpbufs)/sizeof(cpbufs[0]); k++) {
-                       cpbufs[k].bufsize = sizeof(cpbufs[k].stackbuf);
-                       cpbufs[k].buf = cpbufs[k].stackbuf;
+               /*
+                * Use snprintf return value to tell if there
+                * is enough room in the buffer or not.
+                */
+               while ((i = snprintf(intbuf, intbuf_size, "%.0f", tmpval)) >= 
intbuf_size) {
+                       if (intbuf == stackbuf)
+                               intbuf = NULL;
+                       intbuf_size = i + 1;
+                       erealloc(intbuf, char *, intbuf_size, "format_tree");
                }
-       }
 
-       /*
-        * The point of this goop is to grow the buffer
-        * holding the converted number, so that large
-        * values don't overflow a fixed length buffer.
-        */
-#define PREPEND(CH) do {       \
-       if (cp == cpbufs[0].buf) {      \
-               char *prev = cpbufs[0].buf;     \
-               emalloc(cpbufs[0].buf, char *, 2*cpbufs[0].bufsize, \
-                       "format_tree"); \
-               memcpy((cp = cpbufs[0].buf+cpbufs[0].bufsize), prev,    \
-                      cpbufs[0].bufsize);      \
-               cpbufs[0].bufsize *= 2; \
-               if (prev != cpbufs[0].stackbuf) \
-                       efree(prev);    \
-               cend = cpbufs[0].buf+cpbufs[0].bufsize; \
-       }       \
-       *--cp = (CH);   \
-} while(0)
+               if (i < 1)
+                       goto out_of_range;
 
-       /*
-        * Check first for use of `count$'.
-        * If plain argument retrieval was used earlier, choke.
-        *      Otherwise, return the requested argument.
-        * If not `count$' now, but it was used earlier, choke.
-        * If this format is more than total number of args, choke.
-        * Otherwise, return the current argument.
-        */
-#define parse_next_arg() { \
-       if (argnum > 0) { \
-               if (cur_arg > 1) { \
-                       msg(_("fatal: must use `count$' on all formats or 
none")); \
-                       goto out; \
-               } \
-               arg = the_args[argnum]; \
-       } else if (used_dollar) { \
-               msg(_("fatal: must use `count$' on all formats or none")); \
-               arg = 0; /* shutup the compiler */ \
-               goto out; \
-       } else if (cur_arg >= num_args) { \
-               arg = 0; /* shutup the compiler */ \
-               toofew = true; \
-               break; \
-       } else { \
-               arg = the_args[cur_arg]; \
-               cur_arg++; \
-       } \
-}
+               chp = & intbuf[i-1];
+               ii = jj = 0;
+               do {
+                       tmpbuf_prepend(outb, *chp);
+                       chp--; i--;
+#if defined(HAVE_LOCALE_H)
+                       if (spec->quote_flag && loc.grouping[ii] && ++jj == 
loc.grouping[ii]) {
+                               if (i)  /* only add if more digits coming */
+                                       tmpbuf_prepend(outb, 
loc.thousands_sep[0]);     /* XXX - assumption it's one char */
+                               if (loc.grouping[ii+1] == 0)
+                                       jj = 0;         /* keep using current 
val in loc.grouping[ii] */
+                               else if (loc.grouping[ii+1] == CHAR_MAX)
+                                       spec->quote_flag= false;
+                               else {                 
+                                       ii++;
+                                       jj = 0;
+                               }
+                       }
+#endif
+               } while (i > 0);
 
-       need_format = false;
-       used_dollar = false;
 
-       s0 = s1 = fmt_string;
-       while (n0-- > 0) {
-               if (*s1 != '%') {
-                       s1++;
-                       continue;
+               /* add more output digits to match the precision */
+               if (spec->have_prec) {
+                       while (CEND - CP < spec->prec)
+                               tmpbuf_prepend(outb, '0');
                }
-               need_format = true;
-               bchunk(s0, s1 - s0);
-               s0 = s1;
-               cur = &fw;
-               fw = 0;
-               prec = 0;
-               base = 0;
-               argnum = 0;
-               base = 0;
-               have_prec = false;
-               signchar = '\0';
-               zero_flag = false;
-               quote_flag = false;
-               lj = alt = big_flag = bigbig_flag = small_flag = false;
-               fill = sp;
-               cp = cend;
-               chbuf = lchbuf;
-               s1++;
-
-retry:
-               if (n0-- == 0)  /* ran out early! */
-                       break;
-
-               switch (cs1 = *s1++) {
-               case (-1):      /* dummy case to allow for checking */
-check_pos:
-                       if (cur != &fw)
-                               break;          /* reject as a valid format */
-                       goto retry;
-               case '%':
-                       need_format = false;
-                       /*
-                        * 29 Oct. 2002:
-                        * The C99 standard pages 274 and 279 seem to imply that
-                        * since there's no arg converted, the field width 
doesn't
-                        * apply.  The code already was that way, but this
-                        * comment documents it, at least in the code.
-                        */
-                       if (do_lint) {
-                               const char *msg = NULL;
-
-                               if (fw && ! have_prec)
-                                       msg = _("field width is ignored for 
`%%' specifier");
-                               else if (fw == 0 && have_prec)
-                                       msg = _("precision is ignored for `%%' 
specifier");
-                               else if (fw && have_prec)
-                                       msg = _("field width and precision are 
ignored for `%%' specifier");
-
-                               if (msg != NULL)
-                                       lintwarn("%s", msg);
-                       }
-                       bchunk_one("%");
-                       s0 = s1;
-                       break;
-
-               case '0':
-                       /*
-                        * Only turn on zero_flag if we haven't seen
-                        * the field width or precision yet.  Otherwise,
-                        * screws up floating point formatting.
-                        */
-                       if (cur == & fw)
-                               zero_flag = true;
-                       if (lj)
-                               goto retry;
-                       /* FALL through */
-               case '1':
-               case '2':
-               case '3':
-               case '4':
-               case '5':
-               case '6':
-               case '7':
-               case '8':
-               case '9':
-                       if (cur == NULL)
-                               break;
-                       if (prec >= 0)
-                               *cur = cs1 - '0';
-                       /*
-                        * with a negative precision *cur is already set
-                        * to -1, so it will remain negative, but we have
-                        * to "eat" precision digits in any case
-                        */
-                       while (n0 > 0 && *s1 >= '0' && *s1 <= '9') {
-                               --n0;
-                               *cur = *cur * 10 + *s1++ - '0';
-                       }
-                       if (prec < 0)   /* negative precision is discarded */
-                               have_prec = false;
-                       if (cur == &prec)
-                               cur = NULL;
-                       if (n0 == 0)    /* badly formatted control string */
-                               continue;
-                       goto retry;
-               case '$':
-                       if (do_traditional) {
-                               msg(_("fatal: `$' is not permitted in awk 
formats"));
-                               goto out;
-                       }
 
-                       if (cur == &fw) {
-                               argnum = fw;
-                               fw = 0;
-                               used_dollar = true;
-                               if (argnum <= 0) {
-                                       msg(_("fatal: arg count with `$' must 
be > 0"));
-                                       goto out;
-                               }
-                               if (argnum >= num_args) {
-                                       msg(_("fatal: arg count %ld greater 
than total number of supplied arguments"), argnum);
-                                       goto out;
-                               }
-                       } else {
-                               msg(_("fatal: `$' not permitted after period in 
format"));
-                               goto out;
-                       }
+               if (sgn)
+                       tmpbuf_prepend(outb, '-');
+               else if (spec->signchar)
+                       tmpbuf_prepend(outb, spec->signchar);
+               /*
+                * When to fill with zeroes is of course not simple.
+                * First: No zero fill if left-justifying.
+                * Next: There seem to be two cases:
+                *      A '0' without a precision, e.g. %06d
+                *      A precision with no field width, e.g. %.10d
+                * Any other case, we don't want to fill with zeroes.
+                */
+               if (! spec->lj
+                   && ((spec->zero_flag && ! spec->have_prec)
+                        || (spec->fw == 0 && spec->have_prec)))
+                       spec->fill = zero_string;
+               if (spec->prec > spec->fw)
+                       spec->fw = spec->prec;
+               spec->prec = CEND - CP;
+               if (spec->fw > spec->prec && ! spec->lj && spec->fill != 
space_string
+                   && (*CP == '-' || spec->signchar)) {
+                       bchunk_one(outb, CP);
+                       CP++;
+                       spec->prec--;
+                       spec->fw--;
+               }
+               cp = CP;
 
-                       goto retry;
-               case '*':
-                       if (cur == NULL)
-                               break;
-                       if (! do_traditional && isdigit((unsigned char) *s1)) {
-                               int val = 0;
+               pr_num_tail(CP, spec->prec, spec, outb);
+               return 0;
 
-                               for (; n0 > 0 && *s1 && isdigit((unsigned char) 
*s1); s1++, n0--) {
-                                       val *= 10;
-                                       val += *s1 - '0';
-                               }
-                               if (*s1 != '$') {
-                                       msg(_("fatal: no `$' supplied for 
positional field width or precision"));
-                                       goto out;
-                               } else {
-                                       s1++;
-                                       n0--;
-                               }
-                               if (val >= num_args) {
-                                       toofew = true;
-                                       break;
-                               }
-                               arg = the_args[val];
-                       } else {
-                               parse_next_arg();
-                       }
-                       (void) force_number(arg);
-                       *cur = get_number_si(arg);
-                       if (*cur < 0 && cur == &fw) {
-                               *cur = -*cur;
-                               lj++;
-                       }
-                       if (cur == &prec) {
-                               if (*cur >= 0)
-                                       have_prec = true;
-                               else
-                                       have_prec = false;
-                               cur = NULL;
-                       }
-                       goto retry;
-               case ' ':               /* print ' ' or '-' */
-                                       /* 'space' flag is ignored */
-                                       /* if '+' already present  */
-                       if (signchar != false) 
-                               goto check_pos;
-                       /* FALL THROUGH */
-               case '+':               /* print '+' or '-' */
-                       signchar = cs1;
-                       goto check_pos;
-               case '-':
-                       if (prec < 0)
-                               break;
-                       if (cur == &prec) {
-                               prec = -1;
-                               goto retry;
-                       }
-                       fill = sp;      /* if left justified then other */
-                       lj++;           /* filling is ignored */
-                       goto check_pos;
-               case '.':
-                       if (cur != &fw)
-                               break;
-                       cur = &prec;
-                       have_prec = true;
-                       goto retry;
-               case '#':
-                       alt = true;
-                       goto check_pos;
-               case '\'':
-#if defined(HAVE_LOCALE_H)       
-                       /* allow quote_flag if there is a thousands separator. 
*/
-                       if (loc.thousands_sep[0] != '\0')
-                               quote_flag = true;
-                       goto check_pos;
-#else
-                       goto retry;  
-#endif
-               case 'l':
-                       if (big_flag)
-                               break;
-                       else {
-                               static bool warned = false;
-                               
-                               if (do_lint && ! warned) {
-                                       lintwarn(_("`l' is meaningless in awk 
formats; ignored"));
-                                       warned = true;
-                               }
-                               if (do_posix) {
-                                       msg(_("fatal: `l' is not permitted in 
POSIX awk formats"));
-                                       goto out;
-                               }
-                       }
-                       big_flag = true;
-                       goto retry;
-               case 'L':
-                       if (bigbig_flag)
-                               break;
-                       else {
-                               static bool warned = false;
-                               
-                               if (do_lint && ! warned) {
-                                       lintwarn(_("`L' is meaningless in awk 
formats; ignored"));
-                                       warned = true;
-                               }
-                               if (do_posix) {
-                                       msg(_("fatal: `L' is not permitted in 
POSIX awk formats"));
-                                       goto out;
-                               }
-                       }
-                       bigbig_flag = true;
-                       goto retry;
-               case 'h':
-                       if (small_flag)
-                               break;
-                       else {
-                               static bool warned = false;
-                               
-                               if (do_lint && ! warned) {
-                                       lintwarn(_("`h' is meaningless in awk 
formats; ignored"));
-                                       warned = true;
-                               }
-                               if (do_posix) {
-                                       msg(_("fatal: `h' is not permitted in 
POSIX awk formats"));
-                                       goto out;
-                               }
-                       }
-                       small_flag = true;
-                       goto retry;
-               case 'c':
-                       need_format = false;
-                       parse_next_arg();
-                       /* user input that looks numeric is numeric */
-                       if ((arg->flags & (MAYBE_NUM|NUMBER)) == MAYBE_NUM)
-                               (void) force_number(arg);
-                       if ((arg->flags & NUMBER) != 0) {
-                               uval = get_number_uj(arg);
-#if MBS_SUPPORT
-                               if (gawk_mb_cur_max > 1) {
-                                       char buf[100];
-                                       wchar_t wc;
-                                       mbstate_t mbs;
-                                       size_t count;
-
-                                       memset(& mbs, 0, sizeof(mbs));
-                                       wc = uval;
-
-                                       count = wcrtomb(buf, wc, & mbs);
-                                       if (count == 0
-                                           || count == (size_t)-1
-                                           || count == (size_t)-2)
-                                               goto out0;
-
-                                       memcpy(cpbuf, buf, count);
-                                       prec = count;
-                                       cp = cpbuf;
-                                       goto pr_tail;
-                               }
-out0:
-                               ;
-                               /* else,
-                                       fall through */
-#endif
-                               if (do_lint && uval > 255) {
-                                       lintwarn("[s]printf: value %g is too 
big for %%c format",
-                                                       arg->numbr);
-                               }
-                               cpbuf[0] = uval;
-                               prec = 1;
-                               cp = cpbuf;
-                               goto pr_tail;
-                       }
-                       /*
-                        * As per POSIX, only output first character of a
-                        * string value.  Thus, we ignore any provided
-                        * precision, forcing it to 1.  (Didn't this
-                        * used to work? 6/2003.)
-                        */
-                       cp = arg->stptr;
-#if MBS_SUPPORT
-                       /*
-                        * First character can be multiple bytes if
-                        * it's a multibyte character. Grr.
-                        */
-                       if (gawk_mb_cur_max > 1) {
-                               mbstate_t state;
-                               size_t count;
-
-                               memset(& state, 0, sizeof(state));
-                               count = mbrlen(cp, arg->stlen, & state);
-                               if (count == 0
-                                   || count == (size_t)-1
-                                   || count == (size_t)-2)
-                                       goto out2;
-                               prec = count;
-                               goto pr_tail;
-                       }
-out2:
-                       ;
-#endif
-                       prec = 1;
-                       goto pr_tail;
-               case 's':
-                       need_format = false;
-                       parse_next_arg();
-                       arg = force_string(arg);
-                       if (fw == 0 && ! have_prec)
-                               prec = arg->stlen;
-                       else {
-                               char_count = mbc_char_count(arg->stptr, 
arg->stlen);
-                               if (! have_prec || prec > char_count)
-                                       prec = char_count;
-                       }
-                       cp = arg->stptr;
-                       goto pr_tail;
-               case 'd':
-               case 'i':
-                       need_format = false;
-                       parse_next_arg();
-                       (void) force_number(arg);
-                       tmpval = arg->numbr;
-
-                       /*
-                        * Check for Nan or Inf.
-                        */
-                       if (isnan(tmpval) || isinf(tmpval))
+       case 'X':
+               spec->chbuf = Uchbuf;
+               /* FALL THROUGH */
+       case 'x':
+               /* FALL THROUGH */
+       case 'u':
+               /* FALL THROUGH */
+       case 'o':
+               /*
+                * ``The result of converting a zero value with a
+                * precision of zero is no characters.''
+                *
+                * If I remember the ANSI C standard, though,
+                * it says that for octal conversions
+                * the precision is artificially increased
+                * to add an extra 0 if # is supplied.
+                * Indeed, in C,
+                *      printf("%#.0o\n", 0);
+                * prints a single 0.
+                */
+       
+               if (! spec->alt && spec->have_prec && spec->prec == 0 && tmpval 
== 0) {
+                       pr_num_tail(cp, spec->prec, spec, outb);
+                       return 0;
+               }
+
+               if (tmpval < 0) {
+                       uval = (uintmax_t) (intmax_t) tmpval;
+                       if ((AWKNUM)(intmax_t) uval != double_to_int(tmpval))
                                goto out_of_range;
-                       else
-                               tmpval = double_to_int(tmpval);
-
-                       /*
-                        * ``The result of converting a zero value with a
-                        * precision of zero is no characters.''
-                        */
-                       if (have_prec && prec == 0 && tmpval == 0)
-                               goto pr_tail;
-
-                       if (tmpval < 0) {
-                               tmpval = -tmpval;
-                               sgn = true;
-                       } else {
-                               if (tmpval == -0.0)
-                                       /* avoid printing -0 */
-                                       tmpval = 0.0;
-                               sgn = false;
-                       }
-                       /*
-                        * Use snprintf return value to tell if there
-                        * is enough room in the buffer or not.
-                        */
-                       while ((i = snprintf(cpbufs[1].buf,
-                                            cpbufs[1].bufsize, "%.0f",
-                                            tmpval)) >=
-                              cpbufs[1].bufsize) {
-                               if (cpbufs[1].buf == cpbufs[1].stackbuf)
-                                       cpbufs[1].buf = NULL;
-                               if (i > 0) {
-                                       cpbufs[1].bufsize += ((i > 
cpbufs[1].bufsize) ?
-                                                             i : 
cpbufs[1].bufsize);
-                               }
-                               else
-                                       cpbufs[1].bufsize *= 2;
-                               assert(cpbufs[1].bufsize > 0);
-                               erealloc(cpbufs[1].buf, char *,
-                                        cpbufs[1].bufsize, "format_tree");
-                       }
-                       if (i < 1)
+               } else {
+                       uval = (uintmax_t) tmpval;
+                       if ((AWKNUM) uval != double_to_int(tmpval))
                                goto out_of_range;
-                       chp = &cpbufs[1].buf[i-1];
-                       ii = jj = 0;
-                       do {
-                               PREPEND(*chp);
-                               chp--; i--;
-#if defined(HAVE_LOCALE_H)
-                               if (quote_flag && loc.grouping[ii] && ++jj == 
loc.grouping[ii]) {
-                                       if (i)  /* only add if more digits 
coming */
-                                               PREPEND(loc.thousands_sep[0]);  
/* XXX - assumption it's one char */
-                                       if (loc.grouping[ii+1] == 0)
-                                               jj = 0;         /* keep using 
current val in loc.grouping[ii] */
-                                       else if (loc.grouping[ii+1] == CHAR_MAX)
-                                               quote_flag = false;
-                                       else {                 
-                                               ii++;
-                                               jj = 0;
-                                       }
-                               }
-#endif
-                       } while (i > 0);
-
-                       /* add more output digits to match the precision */
-                       if (have_prec) {
-                               while (cend - cp < prec)
-                                       PREPEND('0');
-                       }
+               }
 
-                       if (sgn)
-                               PREPEND('-');
-                       else if (signchar)
-                               PREPEND(signchar);
-                       /*
-                        * When to fill with zeroes is of course not simple.
-                        * First: No zero fill if left-justifying.
-                        * Next: There seem to be two cases:
-                        *      A '0' without a precision, e.g. %06d
-                        *      A precision with no field width, e.g. %.10d
-                        * Any other case, we don't want to fill with zeroes.
-                        */
-                       if (! lj
-                           && ((zero_flag && ! have_prec)
-                                || (fw == 0 && have_prec)))
-                               fill = zero_string;
-                       if (prec > fw)
-                               fw = prec;
-                       prec = cend - cp;
-                       if (fw > prec && ! lj && fill != sp
-                           && (*cp == '-' || signchar)) {
-                               bchunk_one(cp);
-                               cp++;
-                               prec--;
-                               fw--;
-                       }
-                       goto pr_tail;
-               case 'X':
-                       chbuf = Uchbuf; /* FALL THROUGH */
-               case 'x':
-                       base += 6;      /* FALL THROUGH */
-               case 'u':
-                       base += 2;      /* FALL THROUGH */
-               case 'o':
-                       base += 8;
-                       need_format = false;
-                       parse_next_arg();
-                       (void) force_number(arg);
-                       tmpval = arg->numbr;
-
-                       /*
-                        * ``The result of converting a zero value with a
-                        * precision of zero is no characters.''
-                        *
-                        * If I remember the ANSI C standard, though,
-                        * it says that for octal conversions
-                        * the precision is artificially increased
-                        * to add an extra 0 if # is supplied.
-                        * Indeed, in C,
-                        *      printf("%#.0o\n", 0);
-                        * prints a single 0.
-                        */
-                       if (! alt && have_prec && prec == 0 && tmpval == 0)
-                               goto pr_tail;
-
-                       if (tmpval < 0) {
-                               uval = (uintmax_t) (intmax_t) tmpval;
-                               if ((AWKNUM)(intmax_t)uval != 
double_to_int(tmpval))
-                                       goto out_of_range;
-                       } else {
-                               uval = (uintmax_t) tmpval;
-                               if ((AWKNUM)uval != double_to_int(tmpval))
-                                       goto out_of_range;
-                       }
+               /* spec->fmtchar = cs1; */
+               format_nondecimal(uval, spec, outb);
+               return 0;
 
-                       /*
-                        * When to fill with zeroes is of course not simple.
-                        * First: No zero fill if left-justifying.
-                        * Next: There seem to be two cases:
-                        *      A '0' without a precision, e.g. %06d
-                        *      A precision with no field width, e.g. %.10d
-                        * Any other case, we don't want to fill with zeroes.
-                        */
-                       if (! lj
-                           && ((zero_flag && ! have_prec)
-                                || (fw == 0 && have_prec)))
-                               fill = zero_string;
-                       ii = jj = 0;
-                       do {
-                               PREPEND(chbuf[uval % base]);
-                               uval /= base;
-#if defined(HAVE_LOCALE_H)
-                               if (base == 10 && quote_flag && 
loc.grouping[ii] && ++jj == loc.grouping[ii]) {
-                                       if (uval)       /* only add if more 
digits coming */
-                                               PREPEND(loc.thousands_sep[0]);  
/* XXX --- assumption it's one char */
-                                       if (loc.grouping[ii+1] == 0)            
                              
-                                               jj = 0;     /* keep using 
current val in loc.grouping[ii] */
-                                       else if (loc.grouping[ii+1] == 
CHAR_MAX)                        
-                                               quote_flag = false;
-                                       else {                 
-                                               ii++;
-                                               jj = 0;
-                                       }
-                               }
-#endif
-                       } while (uval > 0);
+out_of_range:
+               /* out of range - emergency use of %g format */
+               if (do_lint)
+                       lintwarn(_("[s]printf: value %g is out of range for 
`%%%c' format"),
+                                       (double) tmpval, cs1);
+               cs1 = 'g';
+               goto fmt1;
 
-                       /* add more output digits to match the precision */
-                       if (have_prec) {
-                               while (cend - cp < prec)
-                                       PREPEND('0');
-                       }
-
-                       if (alt && tmpval != 0) {
-                               if (base == 16) {
-                                       PREPEND(cs1);
-                                       PREPEND('0');
-                                       if (fill != sp) {
-                                               bchunk(cp, 2);
-                                               cp += 2;
-                                               fw -= 2;
-                                       }
-                               } else if (base == 8)
-                                       PREPEND('0');
-                       }
-                       base = 0;
-                       if (prec > fw)
-                               fw = prec;
-                       prec = cend - cp;
-       pr_tail:
-                       if (! lj) {
-                               while (fw > prec) {
-                                       bchunk_one(fill);
-                                       fw--;
-                               }
-                       }
-                       copy_count = prec;
-                       if (fw == 0 && ! have_prec)
-                               ;
-                       else if (gawk_mb_cur_max > 1 && (cs1 == 's' || cs1 == 
'c')) {
-                               assert(cp == arg->stptr || cp == cpbuf);
-                               copy_count = mbc_byte_count(arg->stptr, prec);
-                       }
-                       bchunk(cp, copy_count);
-                       while (fw > prec) {
-                               bchunk_one(fill);
-                               fw--;
-                       }
-                       s0 = s1;
-                       break;
-
-     out_of_range:
-                       /* out of range - emergency use of %g format */
-                       if (do_lint)
-                               lintwarn(_("[s]printf: value %g is out of range 
for `%%%c' format"),
-                                                       (double) tmpval, cs1);
-                       cs1 = 'g';
-                       goto fmt1;
-
-               case 'F':
+       case 'F':
 #if ! defined(PRINTF_HAS_F_FORMAT) || PRINTF_HAS_F_FORMAT != 1
-                       cs1 = 'f';
-                       /* FALL THROUGH */
+               cs1 = 'f';
+               /* FALL THROUGH */
 #endif
-               case 'g':
-               case 'G':
-               case 'e':
-               case 'f':
-               case 'E':
-                       need_format = false;
-                       parse_next_arg();
-                       (void) force_number(arg);
-                       tmpval = arg->numbr;
-     fmt1:
-                       if (! have_prec)
-                               prec = DEFAULT_G_PRECISION;
-
-                       chksize(fw + prec + 11);        /* 11 == slop */
-                       cp = cpbuf;
-                       *cp++ = '%';
-                       if (lj)
-                               *cp++ = '-';
-                       if (signchar)
-                               *cp++ = signchar;
-                       if (alt)
-                               *cp++ = '#';
-                       if (zero_flag)
-                               *cp++ = '0';
-                       if (quote_flag)
-                               *cp++ = '\'';
+       case 'g':
+       case 'G':
+       case 'e':
+       case 'f':
+       case 'E':
+fmt1:
+               if (! spec->have_prec)
+                       spec->prec = DEFAULT_G_PRECISION;
+
+               chksize(outb, spec->fw + spec->prec + 11);      /* 11 == slop */
+               cp = CPBUF;     /* XXX --- using the temporary prepend-buffer 
and
+                                * we know it has enough room (>=11).
+                                 */
+               *cp++ = '%';
+               if (spec->lj)
+                       *cp++ = '-';
+               if (spec->signchar)
+                       *cp++ = spec->signchar;
+               if (spec->alt)
+                       *cp++ = '#';
+               if (spec->zero_flag)
+                       *cp++ = '0';
+               if (spec->quote_flag)
+                       *cp++ = '\'';
 
 #if defined(LC_NUMERIC)
-                       if (quote_flag && ! use_lc_numeric)
-                               setlocale(LC_NUMERIC, "");
+               if (spec->quote_flag && ! use_lc_numeric)
+                       setlocale(LC_NUMERIC, "");
 #endif
 
-                       sprintf(cp, "*.*%c", cs1);
-                       while ((nc = snprintf(obufout, ofre, cpbuf,
-                                    (int) fw, (int) prec,
-                                    (double) tmpval)) >= ofre)
-                               chksize(nc)
+               sprintf(cp, "*.*%c", cs1);
+               while ((nc = snprintf(buf_end(outb), buf_space(outb), CPBUF,
+                               (int) spec->fw, (int) spec->prec, tmpval)) >= 
buf_space(outb))
+                       chksize(outb, nc + 1);
 
 #if defined(LC_NUMERIC)
-                       if (quote_flag && ! use_lc_numeric)
-                               setlocale(LC_NUMERIC, "C");
+               if (spec->quote_flag && ! use_lc_numeric)
+                       setlocale(LC_NUMERIC, "C");
 #endif
 
-                       len = strlen(obufout);
-                       ofre -= len;
-                       obufout += len;
-                       s0 = s1;
-                       break;
-               default:
-                       if (do_lint && isalpha(cs1))
-                               lintwarn(_("ignoring unknown format specifier 
character `%c': no argument converted"), cs1);
-                       break;
-               }
-               if (toofew) {
-                       msg("%s\n\t`%s'\n\t%*s%s",
-                             _("fatal: not enough arguments to satisfy format 
string"),
-                             fmt_string, (int) (s1 - fmt_string - 1), "",
-                             _("^ ran out for this one"));
-                       goto out;
-               }
-       }
-       if (do_lint) {
-               if (need_format)
-                       lintwarn(
-                       _("[s]printf: format specifier does not have control 
letter"));
-               if (cur_arg < num_args)
-                       lintwarn(
-                       _("too many arguments supplied for format string"));
-       }
-       bchunk(s0, s1 - s0);
-       r = make_str_node(obuf, obufout - obuf, ALREADY_MALLOCED);
-       obuf = NULL;
-out:
-       {
-               size_t k;
-               size_t count = sizeof(cpbufs)/sizeof(cpbufs[0]);
-               for (k = 0; k < count; k++) {
-                       if (cpbufs[k].buf != cpbufs[k].stackbuf)
-                               efree(cpbufs[k].buf);
-               }
-               if (obuf != NULL)
-                       efree(obuf);
+               buf_adjust(outb, nc); /* adjust data and free space in output 
buffer */
+               return 0;
+
+       default:
+               cant_happen();  
        }
 
-       if (r == NULL)
-               gawk_exit(EXIT_FATAL);
-       return r;
+       return -1;
 }
diff --git a/format.h b/format.h
new file mode 100644
index 0000000..1418a8b
--- /dev/null
+++ b/format.h
@@ -0,0 +1,155 @@
+/* format specification */
+
+struct format_spec {
+       int base;
+       long fw;
+       long prec;
+       const char *fill;
+       const char *chbuf;
+       bool lj;
+       bool alt;
+       bool have_prec;
+       bool zero_flag;
+       bool quote_flag;
+       char signchar;
+       char fmtchar;
+};
+
+
+/* struct to manage awk (s)printf formatted string */
+
+struct print_fmt_buf {
+       char *buf;      /* beginning of buffer */
+       char *dataend;  /* end of current data */
+       size_t bufsize;
+       size_t room_left;
+       bool is_malloced;       /* true if this struct is malloc-ed */
+       void (*chksize)(struct print_fmt_buf *, size_t);
+       void (*cpbuf_chksize)(struct print_fmt_buf *);
+
+       /*
+        * temporary buffer: can be used to prepend one character at a time
+        *      without overflowing the buffer; used primarily to format 
integers.
+        */
+       struct {
+               char *buf;      /* beginning of buffer */
+               char *bufend;   /* end of buffer */
+               size_t bufsize;
+               char *databegin;        /* start of current data */
+       } cpbuf;
+};
+
+extern struct print_fmt_buf *get_fmt_buf(void);
+extern void format_nondecimal(uintmax_t, struct format_spec *, struct 
print_fmt_buf *);
+
+#      define buf_start(ob)    ((ob)->buf)
+#      define buf_end(ob)      ((ob)->dataend)
+#      define buf_space(ob)    ((ob)->room_left)
+#      define cpbuf_start(ob)  ((ob)->cpbuf.databegin)
+#      define cpbuf_end(ob)    ((ob)->cpbuf.bufend)
+#      define cpbuf(ob)        ((ob)->cpbuf.buf)
+
+extern char lchbuf[];
+extern char Uchbuf[];
+extern char space_string[];
+extern char zero_string[];
+
+/* chksize --- make room for something LEN big in the output buffer */
+
+static inline void
+chksize(struct print_fmt_buf *outb, size_t len)
+{
+       if (len > outb->room_left)
+               outb->chksize(outb, len);
+}
+
+/* bchunk --- copy LEN bytes from STR checking for space in the process */
+
+static inline void
+bchunk(struct print_fmt_buf *outb, const char *str, size_t len)
+{
+       if (len > 0) {
+               if (len > outb->room_left)
+                       outb->chksize(outb, len);
+               outb->dataend = (char *) memcpy(outb->dataend, str, len) + len;
+               outb->room_left -= len;
+       }
+}
+
+/* bchunk_one --- copy one byte from STR checking for space in the process */
+
+static inline void
+bchunk_one(struct print_fmt_buf *outb, const char *str)
+{
+       if (1 > outb->room_left)
+               outb->chksize(outb, 1);
+       *outb->dataend++ = *str;
+       --outb->room_left;
+}
+
+/* buf_adjust --- adjust buffer after appending LEN bytes */
+
+static inline void
+buf_adjust(struct print_fmt_buf *outb, size_t len)
+{
+       assert(len <= outb->room_left);
+       outb->dataend += len;
+       outb->room_left -= len;
+}
+
+/* buf2node --- convert bytes to string NODE */
+
+static inline NODE *
+buf2node(struct print_fmt_buf *outb)
+{
+       NODE *node;
+       node = make_str_node(outb->buf, outb->dataend - outb->buf, 
ALREADY_MALLOCED);
+       outb->buf = NULL;
+       return node;
+}
+
+
+/* tmpbuf_prepend --- prepend one byte to temporary buffer */
+
+static inline void
+tmpbuf_prepend(struct print_fmt_buf *outb, char ch)
+{
+       if (outb->cpbuf.databegin == outb->cpbuf.buf)
+               outb->cpbuf_chksize(outb);
+       *--outb->cpbuf.databegin = ch;
+}
+
+/* pr_fill --- fill buffer with current fill character */
+
+static inline void
+pr_fill(struct format_spec *spec, struct print_fmt_buf *outb)
+{
+       while (spec->fw > spec->prec) {
+               bchunk_one(outb, spec->fill);
+               spec->fw--;
+       }
+}
+
+static inline void
+pr_num_tail(const char *cp, size_t copy_count, struct format_spec *spec, 
struct print_fmt_buf *outb)
+{
+       if (! spec->lj)
+               pr_fill(spec, outb);
+       bchunk(outb, cp, copy_count);
+       pr_fill(spec, outb);
+}
+
+
+/* free_print_fmt_buf --- free buffer */
+
+static inline void
+free_fmt_buf(struct print_fmt_buf *outb)
+{
+       if (outb->buf != NULL) {
+               efree(outb->buf);
+               outb->buf = NULL;
+       }
+       efree(outb->cpbuf.buf);
+       if (outb->is_malloced)
+               efree(outb);
+}
diff --git a/main.c b/main.c
index 24dbb56..c7e9a89 100644
--- a/main.c
+++ b/main.c
@@ -1549,7 +1549,7 @@ init_numbr_handler(bltin_t **bltins)
        cmp_numbers = numbr_hndlr->gawk_cmp_numbers;
        str2node = numbr_hndlr->gawk_str2number;
        free_number = numbr_hndlr->gawk_free_number;
-       format_tree = numbr_hndlr->gawk_format_nodes;
+       format_number_printf = numbr_hndlr->gawk_format_printf;
        get_number_d = numbr_hndlr->gawk_todouble;
        get_number_si = numbr_hndlr->gawk_tolong;
        get_number_ui = numbr_hndlr->gawk_toulong;
diff --git a/mpfr.c b/mpfr.c
index 4697a9f..e3215c9 100644
--- a/mpfr.c
+++ b/mpfr.c
@@ -29,6 +29,8 @@
 #include <gmp.h>
 #include <mpfr.h>
 
+#include "format.h"
+
 #ifndef MPFR_RNDN
 /* for compatibility with MPFR 2.X */
 #define MPFR_RNDN GMP_RNDN
@@ -56,12 +58,14 @@ static void mpfp_free_num(NODE *);
 static NODE *mpfp_format_val(const char *, int, NODE *);
 static unsigned long mpfp_toulong(const NODE *);
 static long mpfp_tolong(const NODE *);
-static AWKNUM mpfp_todouble(const NODE *);
+static double mpfp_todouble(const NODE *);
 static uintmax_t mpfp_touintmax_t(const NODE *);
 static int mpfp_sgn(const NODE *);
-static bool mpfp_is_integer(const NODE *n);
+static bool mpfp_isinteger(const NODE *);
+static bool mpfp_isnan(const NODE *);
+static bool mpfp_isinf(const NODE *);
 static NODE *mpfp_copy_number(const NODE *);
-static NODE *mpfp_format_nodes(const char *, size_t, NODE **, long);
+static int mpfp_format_printf(NODE *, struct format_spec *, struct 
print_fmt_buf *);
 static bool mpfp_init(bltin_t **);
 static NODE *mpfp_add(const NODE *, const NODE *);
 static NODE *mpfp_sub(const NODE *, const NODE *);
@@ -162,9 +166,11 @@ numbr_handler_t mpfp_hndlr = {
        mpfp_negate_num,
        mpfp_compare,
        mpfp_sgn,
-       mpfp_is_integer,
+       mpfp_isinteger,
+       mpfp_isnan,
+       mpfp_isinf,
        mpfp_format_val,
-       mpfp_format_nodes,
+       mpfp_format_printf,
        mpfp_todouble,
        mpfp_tolong,
        mpfp_toulong,
@@ -275,7 +281,7 @@ mpfp_tolong(const NODE *n)
 
 /* mpfp_todouble --- conversion to AWKNUM */
 
-static AWKNUM
+static double
 mpfp_todouble(const NODE *n)
 {
        return (n->flags & MPFN) != 0 ? mpfr_get_d(n->qnumbr, ROUND_MODE) : 
mpz_get_d(n->qnumbr);
@@ -299,14 +305,31 @@ mpfp_sgn(const NODE *n)
                : mpz_sgn(MPZ_T(n->qnumbr));
 }
 
-/* mpfp_is_integer --- check if a number is an integer */
+/* mpfp_isinteger --- check if a number is an integer */
 
 static bool
-mpfp_is_integer(const NODE *n)
+mpfp_isinteger(const NODE *n)
 {
        return is_mpfp_integer(n) ? true : mpfr_integer_p(n->qnumbr);
 }
 
+/* mpfp_isnan --- check if a number is NaN */
+
+static bool
+mpfp_isnan(const NODE *n)
+{
+       return is_mpfp_float(n) && mpfr_nan_p(MPFR_T(n->qnumbr));
+}
+
+/* mpfp_isinf --- check if a number is infinity */
+
+static bool
+mpfp_isinf(const NODE *n)
+{
+       return is_mpfp_float(n) && mpfr_inf_p(MPFR_T(n->qnumbr));
+}
+
+
 /* mpfp_make_node --- allocate a node to store MPFR float or GMP integer */
 
 static NODE *
@@ -573,6 +596,7 @@ mpfp_force_number(NODE *n)
        return n;
 }
 
+
 /* mpfp_format_val --- format a numeric value based on format */
 
 static NODE *
@@ -587,10 +611,10 @@ mpfp_format_val(const char *format, int index, NODE *s)
 
        if (is_mpfp_integer(s) || mpfr_integer_p(s->qnumbr)) {
                /* integral value, use %d */
-               r = mpfp_format_nodes("%d", 2, dummy, 2);
+               r = format_tree("%d", 2, dummy, 2);
                s->stfmt = -1;
        } else {
-               r = mpfp_format_nodes(format, fmt_list[index]->stlen, dummy, 2);
+               r = format_tree(format, fmt_list[index]->stlen, dummy, 2);
                assert(r != NULL);
                s->stfmt = (char) index;
        }
@@ -606,6 +630,7 @@ mpfp_format_val(const char *format, int index, NODE *s)
        return s;
 }
 
+
 /* mpfp_str2node --- create an arbitrary-pecision number from string */
 
 static NODE *
@@ -1752,845 +1777,223 @@ finish:
 }
 
 
-
 extern size_t mbc_byte_count(const char *ptr, size_t numchars);
 extern size_t mbc_char_count(const char *ptr, size_t numbytes);
 
-/*
- * mpfp_format_nodes() formats arguments of sprintf,
- * and accordingly to a fmt_string providing a format like in
- * printf family from C library.  Returns a string node which value
- * is a formatted string.  Called by  sprintf function.
- *
- * It is one of the uglier parts of gawk.  Thanks to Michal Jaegermann
- * for taming this beast and making it compatible with ANSI C.
- */
-
-static NODE *
-mpfp_format_nodes(
-       const char *fmt_string,
-       size_t n0,
-       NODE **the_args,
-       long num_args)
-{
-/* copy 'l' bytes from 's' to 'obufout' checking for space in the process */
-/* difference of pointers should be of ptrdiff_t type, but let us be kind */
-#define bchunk(s, l) if (l) { \
-       while ((l) > ofre) { \
-               size_t olen = obufout - obuf; \
-               erealloc(obuf, char *, osiz * 2, "format_tree"); \
-               ofre += osiz; \
-               osiz *= 2; \
-               obufout = obuf + olen; \
-       } \
-       memcpy(obufout, s, (size_t) (l)); \
-       obufout += (l); \
-       ofre -= (l); \
-}
 
-/* copy one byte from 's' to 'obufout' checking for space in the process */
-#define bchunk_one(s) { \
-       if (ofre < 1) { \
-               size_t olen = obufout - obuf; \
-               erealloc(obuf, char *, osiz * 2, "format_tree"); \
-               ofre += osiz; \
-               osiz *= 2; \
-               obufout = obuf + olen; \
-       } \
-       *obufout++ = *s; \
-       --ofre; \
-}
+/* mpfp_format_prinf --- format a number for (s)printf */
 
-/* Is there space for something L big in the buffer? */
-#define chksize(l)  if ((l) >= ofre) { \
-       size_t olen = obufout - obuf; \
-       size_t delta = osiz+l-ofre; \
-       erealloc(obuf, char *, osiz + delta, "format_tree"); \
-       obufout = obuf + olen; \
-       ofre += delta; \
-       osiz += delta; \
-}
+static int
+mpfp_format_printf(NODE *arg, struct format_spec *spec, struct print_fmt_buf 
*outb)
+{
+       mpz_ptr zi;
+       mpfr_ptr mf;
+       enum { MP_INT_WITH_PREC = 1, MP_INT_WITHOUT_PREC, MP_FLOAT } mpfmt_spec;
 
-       size_t cur_arg = 0;
-       NODE *r = NULL;
-       int i, nc;
-       bool toofew = false;
-       char *obuf, *obufout;
-       size_t osiz, ofre;
-       const char *chbuf;
-       const char *s0, *s1;
-       int cs1;
-       NODE *arg;
-       long fw, prec, argnum;
-       bool used_dollar;
-       bool lj, alt, big_flag, bigbig_flag, small_flag, have_prec, need_format;
-       long *cur = NULL;
        uintmax_t uval;
        bool sgn;
-       int base;
-       /*
-        * Although this is an array, the elements serve two different
-        * purposes. The first element is the general buffer meant
-        * to hold the entire result string.  The second one is a
-        * temporary buffer for large floating point values. They
-        * could just as easily be separate variables, and the
-        * code might arguably be clearer.
-        */
-       struct {
-               char *buf;
-               size_t bufsize;
-               char stackbuf[30];
-       } cpbufs[2];
-#define cpbuf  cpbufs[0].buf
-       char *cend = &cpbufs[0].stackbuf[sizeof(cpbufs[0].stackbuf)];
+       int i, ii, jj;
        char *cp;
-       const char *fill;
-       AWKNUM tmpval = 0.0;
-       char signchar = '\0';
-       size_t len;
-       bool zero_flag = false;
-       bool quote_flag = false;
-       int ii, jj;
-       char *chp;
-       size_t copy_count, char_count;
-#ifdef HAVE_MPFR
-       mpz_ptr zi;
-       mpfr_ptr mf;
-#endif
-       enum { MP_INT_WITH_PREC = 1, MP_INT_WITHOUT_PREC, MP_FLOAT } fmt_type;
-
-       static const char sp[] = " ";
-       static const char zero_string[] = "0";
-       static const char lchbuf[] = "0123456789abcdef";
-       static const char Uchbuf[] = "0123456789ABCDEF";
-
-#define INITIAL_OUT_SIZE       512
-       emalloc(obuf, char *, INITIAL_OUT_SIZE, "format_tree");
-       obufout = obuf;
-       osiz = INITIAL_OUT_SIZE;
-       ofre = osiz - 2;
-
-       cur_arg = 1;
-
-       {
-               size_t k;
-               for (k = 0; k < sizeof(cpbufs)/sizeof(cpbufs[0]); k++) {
-                       cpbufs[k].bufsize = sizeof(cpbufs[k].stackbuf);
-                       cpbufs[k].buf = cpbufs[k].stackbuf;
-               }
-       }
-
-       /*
-        * The point of this goop is to grow the buffer
-        * holding the converted number, so that large
-        * values don't overflow a fixed length buffer.
-        */
-#define PREPEND(CH) do {       \
-       if (cp == cpbufs[0].buf) {      \
-               char *prev = cpbufs[0].buf;     \
-               emalloc(cpbufs[0].buf, char *, 2*cpbufs[0].bufsize, \
-                       "format_tree"); \
-               memcpy((cp = cpbufs[0].buf+cpbufs[0].bufsize), prev,    \
-                      cpbufs[0].bufsize);      \
-               cpbufs[0].bufsize *= 2; \
-               if (prev != cpbufs[0].stackbuf) \
-                       efree(prev);    \
-               cend = cpbufs[0].buf+cpbufs[0].bufsize; \
-       }       \
-       *--cp = (CH);   \
-} while(0)
-
-       /*
-        * Check first for use of `count$'.
-        * If plain argument retrieval was used earlier, choke.
-        *      Otherwise, return the requested argument.
-        * If not `count$' now, but it was used earlier, choke.
-        * If this format is more than total number of args, choke.
-        * Otherwise, return the current argument.
-        */
-#define parse_next_arg() { \
-       if (argnum > 0) { \
-               if (cur_arg > 1) { \
-                       msg(_("fatal: must use `count$' on all formats or 
none")); \
-                       goto out; \
-               } \
-               arg = the_args[argnum]; \
-       } else if (used_dollar) { \
-               msg(_("fatal: must use `count$' on all formats or none")); \
-               arg = 0; /* shutup the compiler */ \
-               goto out; \
-       } else if (cur_arg >= num_args) { \
-               arg = 0; /* shutup the compiler */ \
-               toofew = true; \
-               break; \
-       } else { \
-               arg = the_args[cur_arg]; \
-               cur_arg++; \
-       } \
-}
-
-       need_format = false;
-       used_dollar = false;
-
-       s0 = s1 = fmt_string;
-       while (n0-- > 0) {
-               if (*s1 != '%') {
-                       s1++;
-                       continue;
-               }
-               need_format = true;
-               bchunk(s0, s1 - s0);
-               s0 = s1;
-               cur = &fw;
-               fw = 0;
-               prec = 0;
-               base = 0;
-               argnum = 0;
-               base = 0;
-               have_prec = false;
-               signchar = '\0';
-               zero_flag = false;
-               quote_flag = false;
-#ifdef HAVE_MPFR
-               mf = NULL;
-               zi = NULL;
-#endif
-               fmt_type = 0;
+       char cs1;
+       int nc;
+       /* const char *chbuf; */
 
-               lj = alt = big_flag = bigbig_flag = small_flag = false;
-               fill = sp;
-               cp = cend;
-               chbuf = lchbuf;
-               s1++;
+#      define CP               cpbuf_start(outb)
+#      define CEND             cpbuf_end(outb)
+#      define CPBUF            cpbuf(outb)
 
-retry:
-               if (n0-- == 0)  /* ran out early! */
-                       break;
-
-               switch (cs1 = *s1++) {
-               case (-1):      /* dummy case to allow for checking */
-check_pos:
-                       if (cur != &fw)
-                               break;          /* reject as a valid format */
-                       goto retry;
-               case '%':
-                       need_format = false;
-                       /*
-                        * 29 Oct. 2002:
-                        * The C99 standard pages 274 and 279 seem to imply that
-                        * since there's no arg converted, the field width 
doesn't
-                        * apply.  The code already was that way, but this
-                        * comment documents it, at least in the code.
-                        */
-                       if (do_lint) {
-                               const char *msg = NULL;
-
-                               if (fw && ! have_prec)
-                                       msg = _("field width is ignored for 
`%%' specifier");
-                               else if (fw == 0 && have_prec)
-                                       msg = _("precision is ignored for `%%' 
specifier");
-                               else if (fw && have_prec)
-                                       msg = _("field width and precision are 
ignored for `%%' specifier");
-
-                               if (msg != NULL)
-                                       lintwarn("%s", msg);
-                       }
-                       bchunk_one("%");
-                       s0 = s1;
-                       break;
+       spec->fill = space_string;
+       spec->chbuf = lchbuf;
 
-               case '0':
-                       /*
-                        * Only turn on zero_flag if we haven't seen
-                        * the field width or precision yet.  Otherwise,
-                        * screws up floating point formatting.
-                        */
-                       if (cur == & fw)
-                               zero_flag = true;
-                       if (lj)
-                               goto retry;
-                       /* FALL through */
-               case '1':
-               case '2':
-               case '3':
-               case '4':
-               case '5':
-               case '6':
-               case '7':
-               case '8':
-               case '9':
-                       if (cur == NULL)
-                               break;
-                       if (prec >= 0)
-                               *cur = cs1 - '0';
-                       /*
-                        * with a negative precision *cur is already set
-                        * to -1, so it will remain negative, but we have
-                        * to "eat" precision digits in any case
-                        */
-                       while (n0 > 0 && *s1 >= '0' && *s1 <= '9') {
-                               --n0;
-                               *cur = *cur * 10 + *s1++ - '0';
-                       }
-                       if (prec < 0)   /* negative precision is discarded */
-                               have_prec = false;
-                       if (cur == &prec)
-                               cur = NULL;
-                       if (n0 == 0)    /* badly formatted control string */
-                               continue;
-                       goto retry;
-               case '$':
-                       if (do_traditional) {
-                               msg(_("fatal: `$' is not permitted in awk 
formats"));
-                               goto out;
-                       }
+       cs1 = spec->fmtchar;
+       cp = CP;
 
-                       if (cur == &fw) {
-                               argnum = fw;
-                               fw = 0;
-                               used_dollar = true;
-                               if (argnum <= 0) {
-                                       msg(_("fatal: arg count with `$' must 
be > 0"));
-                                       goto out;
-                               }
-                               if (argnum >= num_args) {
-                                       msg(_("fatal: arg count %ld greater 
than total number of supplied arguments"), argnum);
-                                       goto out;
-                               }
-                       } else {
-                               msg(_("fatal: `$' not permitted after period in 
format"));
-                               goto out;
-                       }
-
-                       goto retry;
-               case '*':
-                       if (cur == NULL)
-                               break;
-                       if (! do_traditional && isdigit((unsigned char) *s1)) {
-                               int val = 0;
+       switch (cs1) {
+       case 'd':
+       case 'i':
+               if (is_mpfp_float(arg))
+                       goto mpf0;
+               goto mpz0;
+       case 'X':
+               spec->chbuf = Uchbuf;
+               /* FALL THROUGH */
+       case 'x':
+               /* FALL THROUGH */
+       case 'u':
+               /* FALL THROUGH */
+       case 'o':
+               if (is_mpfp_integer(arg)) {
+mpz0:
+                       zi = arg->qnumbr;
 
-                               for (; n0 > 0 && *s1 && isdigit((unsigned char) 
*s1); s1++, n0--) {
-                                       val *= 10;
-                                       val += *s1 - '0';
-                               }
-                               if (*s1 != '$') {
-                                       msg(_("fatal: no `$' supplied for 
positional field width or precision"));
-                                       goto out;
-                               } else {
-                                       s1++;
-                                       n0--;
-                               }
-                               if (val >= num_args) {
-                                       toofew = true;
-                                       break;
-                               }
-                               arg = the_args[val];
-                       } else {
-                               parse_next_arg();
-                       }
-                       (void) force_number(arg);
-                       *cur = get_number_si(arg);
-                       if (*cur < 0 && cur == &fw) {
-                               *cur = -*cur;
-                               lj++;
-                       }
-                       if (cur == &prec) {
-                               if (*cur >= 0)
-                                       have_prec = true;
-                               else
-                                       have_prec = false;
-                               cur = NULL;
-                       }
-                       goto retry;
-               case ' ':               /* print ' ' or '-' */
-                                       /* 'space' flag is ignored */
-                                       /* if '+' already present  */
-                       if (signchar != false) 
-                               goto check_pos;
-                       /* FALL THROUGH */
-               case '+':               /* print '+' or '-' */
-                       signchar = cs1;
-                       goto check_pos;
-               case '-':
-                       if (prec < 0)
-                               break;
-                       if (cur == &prec) {
-                               prec = -1;
-                               goto retry;
-                       }
-                       fill = sp;      /* if left justified then other */
-                       lj++;           /* filling is ignored */
-                       goto check_pos;
-               case '.':
-                       if (cur != &fw)
-                               break;
-                       cur = &prec;
-                       have_prec = true;
-                       goto retry;
-               case '#':
-                       alt = true;
-                       goto check_pos;
-               case '\'':
-#if defined(HAVE_LOCALE_H)       
-                       /* allow quote_flag if there is a thousands separator. 
*/
-                       if (loc.thousands_sep[0] != '\0')
-                               quote_flag = true;
-                       goto check_pos;
-#else
-                       goto retry;  
-#endif
-               case 'l':
-                       if (big_flag)
-                               break;
-                       else {
-                               static bool warned = false;
-                               
-                               if (do_lint && ! warned) {
-                                       lintwarn(_("`l' is meaningless in awk 
formats; ignored"));
-                                       warned = true;
-                               }
-                               if (do_posix) {
-                                       msg(_("fatal: `l' is not permitted in 
POSIX awk formats"));
-                                       goto out;
-                               }
-                       }
-                       big_flag = true;
-                       goto retry;
-               case 'L':
-                       if (bigbig_flag)
-                               break;
-                       else {
-                               static bool warned = false;
-                               
-                               if (do_lint && ! warned) {
-                                       lintwarn(_("`L' is meaningless in awk 
formats; ignored"));
-                                       warned = true;
-                               }
-                               if (do_posix) {
-                                       msg(_("fatal: `L' is not permitted in 
POSIX awk formats"));
-                                       goto out;
-                               }
-                       }
-                       bigbig_flag = true;
-                       goto retry;
-               case 'h':
-                       if (small_flag)
-                               break;
-                       else {
-                               static bool warned = false;
-                               
-                               if (do_lint && ! warned) {
-                                       lintwarn(_("`h' is meaningless in awk 
formats; ignored"));
-                                       warned = true;
-                               }
-                               if (do_posix) {
-                                       msg(_("fatal: `h' is not permitted in 
POSIX awk formats"));
-                                       goto out;
-                               }
-                       }
-                       small_flag = true;
-                       goto retry;
-               case 'c':
-                       need_format = false;
-                       parse_next_arg();
-                       /* user input that looks numeric is numeric */
-                       if ((arg->flags & (MAYBE_NUM|NUMBER)) == MAYBE_NUM)
-                               (void) force_number(arg);
-                       if ((arg->flags & NUMBER) != 0) {
-                               uval = get_number_uj(arg);
-#if MBS_SUPPORT
-                               if (gawk_mb_cur_max > 1) {
-                                       char buf[100];
-                                       wchar_t wc;
-                                       mbstate_t mbs;
-                                       size_t count;
-
-                                       memset(& mbs, 0, sizeof(mbs));
-                                       wc = uval;
-
-                                       count = wcrtomb(buf, wc, & mbs);
-                                       if (count == 0
-                                           || count == (size_t)-1
-                                           || count == (size_t)-2)
-                                               goto out0;
-
-                                       memcpy(cpbuf, buf, count);
-                                       prec = count;
-                                       cp = cpbuf;
-                                       goto pr_tail;
-                               }
-out0:
-                               ;
-                               /* else,
-                                       fall through */
-#endif
-                               if (do_lint && uval > 255) {
-                                       lintwarn("[s]printf: value %g is too 
big for %%c format",
-                                                       arg->numbr);
+                       if (cs1 != 'd' && cs1 != 'i') {
+                               if (mpz_sgn(zi) <= 0) {
+                                       /*
+                                        * Negative value or 0 requires special 
handling.
+                                        * Unlike MPFR, GMP does not allow 
conversion
+                                        * to (u)intmax_t. So we first convert 
GMP type to
+                                        * a MPFR type.
+                                        */
+                                       mf = mpz2mpfr(zi, _mpfrval);
+                                       goto mpf1;
                                }
-                               cpbuf[0] = uval;
-                               prec = 1;
-                               cp = cpbuf;
-                               goto pr_tail;
-                       }
-                       /*
-                        * As per POSIX, only output first character of a
-                        * string value.  Thus, we ignore any provided
-                        * precision, forcing it to 1.  (Didn't this
-                        * used to work? 6/2003.)
-                        */
-                       cp = arg->stptr;
-#if MBS_SUPPORT
-                       /*
-                        * First character can be multiple bytes if
-                        * it's a multibyte character. Grr.
-                        */
-                       if (gawk_mb_cur_max > 1) {
-                               mbstate_t state;
-                               size_t count;
-
-                               memset(& state, 0, sizeof(state));
-                               count = mbrlen(cp, arg->stlen, & state);
-                               if (count == 0
-                                   || count == (size_t)-1
-                                   || count == (size_t)-2)
-                                       goto out2;
-                               prec = count;
-                               goto pr_tail;
-                       }
-out2:
-                       ;
-#endif
-                       prec = 1;
-                       goto pr_tail;
-               case 's':
-                       need_format = false;
-                       parse_next_arg();
-                       arg = force_string(arg);
-                       if (fw == 0 && ! have_prec)
-                               prec = arg->stlen;
-                       else {
-                               char_count = mbc_char_count(arg->stptr, 
arg->stlen);
-                               if (! have_prec || prec > char_count)
-                                       prec = char_count;
-                       }
-                       cp = arg->stptr;
-                       goto pr_tail;
-               case 'd':
-               case 'i':
-                       need_format = false;
-                       parse_next_arg();
-                       (void) force_number(arg);
-#ifdef HAVE_MPFR
-                       if (is_mpfp_float(arg))
-                               goto mpf0;
-                       else {
-                               assert(is_mpfp_integer(arg) == true);
-                               goto mpz0;
+                               spec->signchar = '\0';  /* Don't print '+' */
                        }
-#endif
-               case 'X':
-                       chbuf = Uchbuf; /* FALL THROUGH */
-               case 'x':
-                       base += 6;      /* FALL THROUGH */
-               case 'u':
-                       base += 2;      /* FALL THROUGH */
-               case 'o':
-                       base += 8;
-                       need_format = false;
-                       parse_next_arg();
-                       (void) force_number(arg);
-#ifdef HAVE_MPFR
-                       if (is_mpfp_integer(arg)) {
-mpz0:
-                               zi = arg->qnumbr;
-
-                               if (cs1 != 'd' && cs1 != 'i') {
-                                       if (mpz_sgn(zi) <= 0) {
-                                               /*
-                                                * Negative value or 0 requires 
special handling.
-                                                * Unlike MPFR, GMP does not 
allow conversion
-                                                * to (u)intmax_t. So we first 
convert GMP type to
-                                                * a MPFR type.
-                                                */
-                                               mf = mpz2mpfr(zi, _mpfrval);
-                                               goto mpf1;
-                                       }
-                                       signchar = '\0';        /* Don't print 
'+' */
-                               }
 
-                               /* See comments above about when to fill with 
zeros */
-                               zero_flag = (! lj
-                                                   && ((zero_flag && ! 
have_prec)
-                                                        || (fw == 0 && 
have_prec)));
+                       /* See comments above about when to fill with zeros */
+                       spec->zero_flag = (! spec->lj
+                                           && ((spec->zero_flag && ! 
spec->have_prec)
+                                                || (spec->fw == 0 && 
spec->have_prec)));
 
-                               fmt_type = have_prec ? MP_INT_WITH_PREC : 
MP_INT_WITHOUT_PREC;
-                               goto fmt0;
+                       mpfmt_spec = spec->have_prec ? MP_INT_WITH_PREC : 
MP_INT_WITHOUT_PREC;
+                       goto fmt0;
 
-                       } else {
-                               assert(is_mpfp_float(arg) == true);
+               } else {
+                       assert(is_mpfp_float(arg) == true);
 mpf0:
-                               mf = arg->qnumbr;
-                               if (! mpfr_number_p(mf)) {
-                                       /* inf or NaN */
-                                       cs1 = 'g';
-                                       fmt_type = MP_FLOAT;
-                                       goto fmt1;
-                               }
+                       mf = arg->qnumbr;
+                       if (! mpfr_number_p(mf)) {
+                               /* inf or NaN */
+                               cs1 = 'g';
+                               mpfmt_spec = MP_FLOAT;
+                               goto fmt1;
+                       }
 
-                               if (cs1 != 'd' && cs1 != 'i') {
+                       if (cs1 != 'd' && cs1 != 'i') {
 mpf1:
-                                       /*
-                                        * The output of printf("%#.0x", 0) is 
0 instead of 0x, hence <= in
-                                        * the comparison below.
-                                        */
-                                       if (mpfr_sgn(mf) <= 0) {
-                                               if (! mpfr_fits_intmax_p(mf, 
ROUND_MODE)) {
-                                                       /* -ve number is too 
large */
-                                                       cs1 = 'g';
-                                                       fmt_type = MP_FLOAT;
-                                                       goto fmt1;
-                                               }
-
-                                               tmpval = uval = (uintmax_t) 
mpfr_get_sj(mf, ROUND_MODE);
-                                               if (! alt && have_prec && prec 
== 0 && tmpval == 0)
-                                                       goto pr_tail;   /* 
printf("%.0x", 0) is no characters */
-                                               goto int0;
+                               /*
+                                * The output of printf("%#.0x", 0) is 0 
instead of 0x, hence <= in
+                                * the comparison below.
+                                */
+                               if (mpfr_sgn(mf) <= 0) {
+                                       if (! mpfr_fits_intmax_p(mf, 
ROUND_MODE)) {
+                                               /* -ve number is too large */
+                                               cs1 = 'g';
+                                               mpfmt_spec = MP_FLOAT;
+                                               goto fmt1;
                                        }
-                                       signchar = '\0';        /* Don't print 
'+' */
-                               }
 
-                               /* See comments above about when to fill with 
zeros */
-                               zero_flag = (! lj
-                                                   && ((zero_flag && ! 
have_prec)
-                                                        || (fw == 0 && 
have_prec)));
-                               
-                               (void) mpfr_get_z(_mpzval, mf, MPFR_RNDZ);      
/* convert to GMP integer */
-                               fmt_type = have_prec ? MP_INT_WITH_PREC : 
MP_INT_WITHOUT_PREC;
-                               zi = _mpzval;
-                               goto fmt0;
-                       }
-#endif
-
-#ifdef HAVE_MPFR
-       int0:
-#endif
-                       /*
-                        * When to fill with zeroes is of course not simple.
-                        * First: No zero fill if left-justifying.
-                        * Next: There seem to be two cases:
-                        *      A '0' without a precision, e.g. %06d
-                        *      A precision with no field width, e.g. %.10d
-                        * Any other case, we don't want to fill with zeroes.
-                        */
-                       if (! lj
-                           && ((zero_flag && ! have_prec)
-                                || (fw == 0 && have_prec)))
-                               fill = zero_string;
-                       ii = jj = 0;
-                       do {
-                               PREPEND(chbuf[uval % base]);
-                               uval /= base;
-#if defined(HAVE_LOCALE_H)
-                               if (base == 10 && quote_flag && 
loc.grouping[ii] && ++jj == loc.grouping[ii]) {
-                                       if (uval)       /* only add if more 
digits coming */
-                                               PREPEND(loc.thousands_sep[0]);  
/* XXX --- assumption it's one char */
-                                       if (loc.grouping[ii+1] == 0)            
                              
-                                               jj = 0;     /* keep using 
current val in loc.grouping[ii] */
-                                       else if (loc.grouping[ii+1] == 
CHAR_MAX)                        
-                                               quote_flag = false;
-                                       else {                 
-                                               ii++;
-                                               jj = 0;
+                                       uval = (uintmax_t) mpfr_get_sj(mf, 
ROUND_MODE);
+                                       if (! spec->alt && spec->have_prec && 
spec->prec == 0 && uval == 0) {
+                                               /* printf("%.0x", 0) is no 
characters */
+                                               pr_num_tail(cp, 0, spec, outb);
+                                               return 0;
                                        }
-                               }
-#endif
-                       } while (uval > 0);
-
-                       /* add more output digits to match the precision */
-                       if (have_prec) {
-                               while (cend - cp < prec)
-                                       PREPEND('0');
-                       }
 
-                       if (alt && tmpval != 0) {
-                               if (base == 16) {
-                                       PREPEND(cs1);
-                                       PREPEND('0');
-                                       if (fill != sp) {
-                                               bchunk(cp, 2);
-                                               cp += 2;
-                                               fw -= 2;
-                                       }
-                               } else if (base == 8)
-                                       PREPEND('0');
-                       }
-                       base = 0;
-                       if (prec > fw)
-                               fw = prec;
-                       prec = cend - cp;
-       pr_tail:
-                       if (! lj) {
-                               while (fw > prec) {
-                                       bchunk_one(fill);
-                                       fw--;
+                                       /* spec->fmtchar = cs1; */
+                                       /* spec->chbuf = chbuf; */
+                                       format_nondecimal(uval, spec, outb);
+                                       return 0;
                                }
+                               spec->signchar = '\0';  /* Don't print '+' */
                        }
-                       copy_count = prec;
-                       if (fw == 0 && ! have_prec)
-                               ;
-                       else if (gawk_mb_cur_max > 1 && (cs1 == 's' || cs1 == 
'c')) {
-                               assert(cp == arg->stptr || cp == cpbuf);
-                               copy_count = mbc_byte_count(arg->stptr, prec);
-                       }
-                       bchunk(cp, copy_count);
-                       while (fw > prec) {
-                               bchunk_one(fill);
-                               fw--;
-                       }
-                       s0 = s1;
-                       break;
-
-     out_of_range:
-                       /* out of range - emergency use of %g format */
-                       if (do_lint)
-                               lintwarn(_("[s]printf: value %g is out of range 
for `%%%c' format"),
-                                                       (double) tmpval, cs1);
-                       cs1 = 'g';
-                       goto fmt1;
 
-               case 'F':
-#if ! defined(PRINTF_HAS_F_FORMAT) || PRINTF_HAS_F_FORMAT != 1
-                       cs1 = 'f';
-                       /* FALL THROUGH */
-#endif
-               case 'g':
-               case 'G':
-               case 'e':
-               case 'f':
-               case 'E':
-                       need_format = false;
-                       parse_next_arg();
-                       (void) force_number(arg);
+                       /* See comments above about when to fill with zeros */
+                       spec->zero_flag = (! spec->lj
+                                           && ((spec->zero_flag && ! 
spec->have_prec)
+                                                || (spec->fw == 0 && 
spec->have_prec)));
+                       
+                       (void) mpfr_get_z(_mpzval, mf, MPFR_RNDZ);      /* 
convert to GMP integer */
+                       mpfmt_spec = spec->have_prec ? MP_INT_WITH_PREC : 
MP_INT_WITHOUT_PREC;
+                       zi = _mpzval;
+                       goto fmt0;
+               }
 
-                       if (! is_mpfp_number(arg))
-                               tmpval = arg->numbr;
-#ifdef HAVE_MPFR
-                       else if (is_mpfp_float(arg)) {
-                               mf = arg->qnumbr;
-                               fmt_type = MP_FLOAT;
-                       } else {
-                               /* arbitrary-precision integer, convert to MPFR 
float */
-                               assert(mf == NULL);
-                               mf = mpz2mpfr(arg->qnumbr, _mpfrval);
-                               fmt_type = MP_FLOAT;
-                       }
-#endif
-     fmt1:
-                       if (! have_prec)
-                               prec = DEFAULT_G_PRECISION;
-#ifdef HAVE_MPFR
-     fmt0:
-#endif
-                       chksize(fw + prec + 11);        /* 11 == slop */
-                       cp = cpbuf;
-                       *cp++ = '%';
-                       if (lj)
-                               *cp++ = '-';
-                       if (signchar)
-                               *cp++ = signchar;
-                       if (alt)
-                               *cp++ = '#';
-                       if (zero_flag)
-                               *cp++ = '0';
-                       if (quote_flag)
-                               *cp++ = '\'';
+#if 0
+out_of_range:
+               /* out of range - emergency use of %g format */
+               if (do_lint)
+                       lintwarn(_("[s]printf: value %g is out of range for 
`%%%c' format"),
+                                               (double) tmpval, cs1);
+               cs1 = 'g';
+               goto fmt1;
 
-#if defined(LC_NUMERIC)
-                       if (quote_flag && ! use_lc_numeric)
-                               setlocale(LC_NUMERIC, "");
 #endif
 
-                       switch (fmt_type) {
-#ifdef HAVE_MPFR
-                       case MP_INT_WITH_PREC:
-                               sprintf(cp, "*.*Z%c", cs1);
-                               while ((nc = mpfr_snprintf(obufout, ofre, cpbuf,
-                                            (int) fw, (int) prec, zi)) >= ofre)
-                                       chksize(nc)
-                               break;
-                       case MP_INT_WITHOUT_PREC:
-                               sprintf(cp, "*Z%c", cs1);
-                               while ((nc = mpfr_snprintf(obufout, ofre, cpbuf,
-                                            (int) fw, zi)) >= ofre)
-                                       chksize(nc)
-                               break;
-                       case MP_FLOAT:
-                               sprintf(cp, "*.*R*%c", cs1);
-                               while ((nc = mpfr_snprintf(obufout, ofre, cpbuf,
-                                            (int) fw, (int) prec, ROUND_MODE, 
mf)) >= ofre)
-                                       chksize(nc)
-                               break;
+       case 'F':
+#if ! defined(PRINTF_HAS_F_FORMAT) || PRINTF_HAS_F_FORMAT != 1
+               cs1 = 'f';
+               /* FALL THROUGH */
 #endif
-                       default:
-                               sprintf(cp, "*.*%c", cs1);
-                               while ((nc = snprintf(obufout, ofre, cpbuf,
-                                            (int) fw, (int) prec,
-                                            (double) tmpval)) >= ofre)
-                                       chksize(nc)
-                       }
+       case 'g':
+       case 'G':
+       case 'e':
+       case 'f':
+       case 'E':
+               if (is_mpfp_float(arg)) {
+                       mf = arg->qnumbr;
+                       mpfmt_spec = MP_FLOAT;
+               } else {
+                       /* arbitrary-precision integer, convert to MPFR float */
+                       assert(mf == NULL);
+                       mf = mpz2mpfr(arg->qnumbr, _mpfrval);
+                       mpfmt_spec = MP_FLOAT;
+               }
+fmt1:
+               if (! spec->have_prec)
+                       spec->prec = DEFAULT_G_PRECISION;
+
+fmt0:
+               chksize(outb, spec->fw + spec->prec + 11);      /* 11 == slop */
+               cp = CPBUF;     /* XXX --- using the temporary prepend-buffer 
and
+                                * we know it has enough room (>=11).
+                                 */
+               *cp++ = '%';
+               if (spec->lj)
+                       *cp++ = '-';
+               if (spec->signchar)
+                       *cp++ = spec->signchar;
+               if (spec->alt)
+                       *cp++ = '#';
+               if (spec->zero_flag)
+                       *cp++ = '0';
+               if (spec->quote_flag)
+                       *cp++ = '\'';
 
 #if defined(LC_NUMERIC)
-                       if (quote_flag && ! use_lc_numeric)
-                               setlocale(LC_NUMERIC, "C");
+               if (spec->quote_flag && ! use_lc_numeric)
+                       setlocale(LC_NUMERIC, "");
 #endif
 
-                       len = strlen(obufout);
-                       ofre -= len;
-                       obufout += len;
-                       s0 = s1;
+               switch (mpfmt_spec) {
+               case MP_INT_WITH_PREC:
+                       sprintf(cp, "*.*Z%c", cs1);
+                       while ((nc = mpfr_snprintf(buf_end(outb), 
buf_space(outb), CPBUF,
+                                       (int) spec->fw, (int) spec->prec, zi)) 
>= buf_space(outb))
+                               chksize(outb, nc + 1);
                        break;
-               default:
-                       if (do_lint && isalpha(cs1))
-                               lintwarn(_("ignoring unknown format specifier 
character `%c': no argument converted"), cs1);
+               case MP_INT_WITHOUT_PREC:
+                       sprintf(cp, "*Z%c", cs1);
+                       while ((nc = mpfr_snprintf(buf_end(outb), 
buf_space(outb), CPBUF,
+                                       (int) spec->fw, zi)) >= buf_space(outb))
+                               chksize(outb, nc + 1);
                        break;
+               case MP_FLOAT:
+                       sprintf(cp, "*.*R*%c", cs1);
+                       while ((nc = mpfr_snprintf(buf_end(outb), 
buf_space(outb), CPBUF,
+                                       (int) spec->fw, (int) spec->prec, 
ROUND_MODE, mf)) >= buf_space(outb))
+                               chksize(outb, nc + 1);
+                       break;
+               default:
+                       cant_happen();
                }
-               if (toofew) {
-                       msg("%s\n\t`%s'\n\t%*s%s",
-                             _("fatal: not enough arguments to satisfy format 
string"),
-                             fmt_string, (int) (s1 - fmt_string - 1), "",
-                             _("^ ran out for this one"));
-                       goto out;
-               }
-       }
-       if (do_lint) {
-               if (need_format)
-                       lintwarn(
-                       _("[s]printf: format specifier does not have control 
letter"));
-               if (cur_arg < num_args)
-                       lintwarn(
-                       _("too many arguments supplied for format string"));
-       }
-       bchunk(s0, s1 - s0);
-       r = make_str_node(obuf, obufout - obuf, ALREADY_MALLOCED);
-       obuf = NULL;
-out:
-       {
-               size_t k;
-               size_t count = sizeof(cpbufs)/sizeof(cpbufs[0]);
-               for (k = 0; k < count; k++) {
-                       if (cpbufs[k].buf != cpbufs[k].stackbuf)
-                               efree(cpbufs[k].buf);
-               }
-               if (obuf != NULL)
-                       efree(obuf);
+
+#if defined(LC_NUMERIC)
+               if (spec->quote_flag && ! use_lc_numeric)
+                       setlocale(LC_NUMERIC, "C");
+#endif
+
+               buf_adjust(outb, nc); /* adjust data and free space in output 
buffer */
+               return 0;
+
+       default:
+               cant_happen();
        }
 
-       if (r == NULL)
-               gawk_exit(EXIT_FATAL);
-       return r;
+       return -1;
 }
 
+
 #else
 
 static bool mpfp_init(bltin_t **bltins);
diff --git a/msg.c b/msg.c
index dd83759..4d67f60 100644
--- a/msg.c
+++ b/msg.c
@@ -91,8 +91,9 @@ err(bool isfatal, const char *s, const char *emsg, va_list 
argp)
 
 /*
  * fmt_number --- format a number node for use in error messages.
- *     N.B: format is awk printf format. MUST NOT throw warning in format
- *     tree. "%ld" is BAD, "%d" is Ok!
+ *
+ * N.B: format is awk printf format. MUST NOT generate warning
+ * in format_tree(). "%ld" is BAD, "%d" is OK!
  */
 
 const char *
diff --git a/node.c b/node.c
index 9efd767..55d7710 100644
--- a/node.c
+++ b/node.c
@@ -26,7 +26,7 @@
 
 #include "awk.h"
 
-NODE *(*format_tree)(const char *, size_t, NODE **, long);
+int (*format_number_printf)(NODE *, struct format_spec *, struct print_fmt_buf 
*);
 NODE *(*str2node)(char *, char **, int, bool);
 NODE *(*make_number)(AWKNUM);
 NODE *(*str2number)(NODE *);

-----------------------------------------------------------------------

Summary of changes:
 ChangeLog |   22 ++
 awk.h     |   19 +-
 builtin.c |  637 +++++++++++++++++++++++++++++++++++++
 double.c  | 1036 ++++++++++++++-----------------------------------------------
 format.h  |  155 +++++++++
 main.c    |    2 +-
 mpfr.c    | 1011 ++++++++++++-----------------------------------------------
 msg.c     |    5 +-
 node.c    |    2 +-
 9 files changed, 1267 insertions(+), 1622 deletions(-)
 create mode 100644 format.h


hooks/post-receive
-- 
gawk



reply via email to

[Prev in Thread] Current Thread [Next in Thread]