root/usr/src/ucblib/libucb/port/stdio/doprnt.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*      Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/*        All Rights Reserved   */

/*
 * University Copyright- Copyright (c) 1982, 1986, 1988
 * The Regents of the University of California
 * All Rights Reserved
 *
 * University Acknowledgment- Portions of this document are derived from
 * software developed by the University of California, Berkeley, and its
 * contributors.
 */

/*
 *      _doprnt: common code for printf, fprintf, sprintf
 */

#include <sys/types.h>
#include "file64.h"
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <stdarg.h>
#include <values.h>
#include <nan.h>
#include <memory.h>
#include <string.h>
#include "print.h"      /* parameters & macros for doprnt */
#include "stdiom.h"
#include <locale.h>
#include <stddef.h>
#include "_locale.h"
#include "libc.h"

#define PUT(p, n)       { unsigned char *newbufptr; \
                        if ((newbufptr = bufptr + (n)) > bufferend) { \
                                _dowrite((p), (n), iop, &bufptr); \
                        } else { \
                                (void) memcpy(bufptr, (p), (n)); \
                                bufptr = newbufptr; \
                        } \
                        }
#define PAD(s, n)       { int nn; \
                        for (nn = (n); nn > 20; nn -= 20) \
                                _dowrite((s), 20, iop, &bufptr); \
                        PUT((s), nn); \
                        }

#define SNLEN   5       /* Length of string used when printing a NaN */

/* bit positions for flags used in doprnt */

#define LENGTH  1       /* l */
#define FPLUS   2       /* + */
#define FMINUS    4     /* - */
#define FBLANK    8     /* blank */
#define FSHARP   16     /* # */
#define PADZERO  32     /* padding zeroes requested via '0' */
#define DOTSEEN  64     /* dot appeared in format specification */
#define SUFFIX  128     /* a suffix is to appear in the output */
#define RZERO   256     /* there will be trailing zeros in output */
#define LZERO   512     /* there will be leading zeroes in output */
#define SHORT  1024     /* h */

/*
 *      Positional Parameter information
 */
#define MAXARGS 30      /* max. number of args for fast positional paramters */

/*
 * stva_list is used to subvert C's restriction that a variable with an
 * array type can not appear on the left hand side of an assignment operator.
 * By putting the array inside a structure, the functionality of assigning to
 * the whole array through a simple assignment is achieved..
 */
typedef struct stva_list {
        va_list ap;
} stva_list;

static char _blanks[] = "                    ";
static char _zeroes[] = "00000000000000000000";
static char uc_digs[] = "0123456789ABCDEF";
static char lc_digs[] = "0123456789abcdef";
static char  lc_nan[] = "nan0x";
static char  uc_nan[] = "NAN0X";
static char  lc_inf[] = "inf";
static char  uc_inf[] = "INF";

/*
 * forward declarations
 */
void _mkarglst(char *, stva_list, stva_list []);
void _getarg(char *, stva_list *, int);
static int _lowdigit(long *);
static void _dowrite(char *, ssize_t, FILE *, unsigned char **);

static int
_lowdigit(long *valptr)
{       /* This function computes the decimal low-order digit of the number */
        /* pointed to by valptr, and returns this digit after dividing   */
        /* *valptr by ten.  This function is called ONLY to compute the */
        /* low-order digit of a long whose high-order bit is set. */

        int lowbit = (int)(*valptr & 1);
        long value = (*valptr >> 1) & ~HIBITL;

        *valptr = value / 5;
        return ((int)(value % 5 * 2 + lowbit + '0'));
}

/* The function _dowrite carries out buffer pointer bookkeeping surrounding */
/* a call to fwrite.  It is called only when the end of the file output */
/* buffer is approached or in other unusual situations. */
static void
_dowrite(char *p, ssize_t n, FILE *iop, unsigned char **ptrptr)
{
        if (!(iop->_flag & _IOREAD)) {
                iop->_cnt -= (*ptrptr - iop->_ptr);
                iop->_ptr = *ptrptr;
                _bufsync(iop, _bufend(iop));
                (void) fwrite(p, 1, n, iop);
                *ptrptr = iop->_ptr;
        } else
                *ptrptr = (unsigned char *) memcpy(*ptrptr, p, n) + n;
}

int
_doprnt(char *format, va_list in_args, FILE *iop)
{

        /* bufptr is used inside of doprnt instead of iop->_ptr; */
        /* bufferend is a copy of _bufend(iop), if it exists.  For */
        /* dummy file descriptors (iop->_flag & _IOREAD), bufferend */
        /* may be meaningless. Dummy file descriptors are used so that */
        /* sprintf and vsprintf may share the _doprnt routine with the */
        /* rest of the printf family. */

        unsigned char *bufptr;
        unsigned char *bufferend;

        /* This variable counts output characters. */
        int     count = 0;

        /* Starting and ending points for value to be printed */
        char    *bp;
        char    *p;

        /* Field width and precision */
        int     width, prec;

        /* Format code */
        int     fcode;

        /* Number of padding zeroes required on the left and right */
        int     lzero, rzero;

        /* Flags - bit positions defined by LENGTH, FPLUS, FMINUS, FBLANK, */
        /* and FSHARP are set if corresponding character is in format */
        /* Bit position defined by PADZERO means extra space in the field */
        /* should be padded with leading zeroes rather than with blanks */
        int     flagword;

        /* Values are developed in this buffer */
        char    buf[max(MAXDIGS, 1+max(MAXFCVT+MAXEXP, MAXECVT))];

        /* Pointer to sign, "0x", "0X", or empty */
        char    *prefix;

        /* Exponent or empty */
        char    *suffix;

        /* Buffer to create exponent */
        char    expbuf[MAXESIZ + 1];

        /* Length of prefix and of suffix */
        int     prefixlength, suffixlength;

        /* Combined length of leading zeroes, trailing zeroes, and suffix */
        int     otherlength;

        /* The value being converted, if integer */
        long    val;

        /* The value being converted, if real */
        double  dval;

        /* Output values from fcvt and ecvt */
        int     decpt, sign;

        /* Pointer to a translate table for digits of whatever radix */
        char    *tab;

        /* Work variables */
        int     k, lradix, mradix;

        /* Variables used to flag an infinities and nans, resp. */
        /* Nan_flg is used with two purposes: to flag a NaN and */
        /* as the length of the string ``NAN0X'' (``nan0x'') */
        int      inf_nan = 0, NaN_flg = 0;

        /* Pointer to string "NAN0X" or "nan0x" */
        char     *SNAN;

        /* Flag for negative infinity or NaN */
        int neg_in = 0;

        /* variables for positional parameters */
        char    *sformat = format;      /* save the beginning of the format */
        int     fpos = 1;               /* 1 if first positional parameter */
        stva_list args;         /* used to step through the argument list */
        stva_list sargs;
                /* used to save the start of the argument list */
        stva_list bargs;
                /* used to restore args if positional width or precision */
        stva_list arglst[MAXARGS];
                /*
                 * array giving the appropriate values for va_arg() to
                 * retrieve the corresponding argument:
                 * arglst[0] is the first argument,
                 * arglst[1] is the second argument, etc.
                 */
        int     starflg = 0;    /* set to 1 if * format specifier seen */
        /*
         * Initialize args and sargs to the start of the argument list.
         * Note that ANSI guarantees that the address of the first member of
         * a structure will be the same as the address of the structure.
         * See equivalent code in libc doprnt.c
         */

#if !(defined(__amd64) && defined(__GNUC__))    /* XX64 - fix me */
        va_copy(args.ap, in_args);
#endif
        sargs = args;

        /* if first I/O to the stream get a buffer */
        /* Note that iop->_base should not equal 0 for sprintf and vsprintf */
        if (iop->_base == 0 && _findbuf(iop) == 0)
                return (EOF);

        /* initialize buffer pointer and buffer end pointer */
        bufptr = iop->_ptr;
        bufferend = (iop->_flag & _IOREAD) ?
            (unsigned char *)((long)bufptr | (-1L & ~HIBITL))
            : _bufend(iop);

        /*
         *      The main loop -- this loop goes through one iteration
         *      for each string of ordinary characters or format specification.
         */
        for (;;) {
                ptrdiff_t pdiff;

                if ((fcode = *format) != '\0' && fcode != '%') {
                        bp = format;
                        do {
                                format++;
                        } while ((fcode = *format) != '\0' && fcode != '%');

                        pdiff = format - bp;
                                /* pdiff = no. of non-% chars */
                        count += pdiff;
                        PUT(bp, pdiff);
                }
                if (fcode == '\0') {  /* end of format; return */
                        ptrdiff_t d = bufptr - iop->_ptr;
                        iop->_cnt -= d;
                        iop->_ptr = bufptr;
                        if (bufptr + iop->_cnt > bufferend &&
                            !(iop->_flag & _IOREAD))
                                _bufsync(iop, bufferend);
                                /*
                                 * in case of interrupt during last
                                 * several lines
                                 */
                        if (iop->_flag & (_IONBF | _IOLBF) &&
                            (iop->_flag & _IONBF ||
                            memchr((char *)(bufptr-count), '\n', count) !=
                            NULL))
                                (void) _xflsbuf(iop);
                        return (ferror(iop) ? EOF : count);
                }

                /*
                 *      % has been found.
                 *      The following switch is used to parse the format
                 *      specification and to perform the operation specified
                 *      by the format letter.  The program repeatedly goes
                 *      back to this switch until the format letter is
                 *      encountered.
                 */
                width = prefixlength = otherlength = flagword =
                    suffixlength = 0;
                format++;

        charswitch:

                switch (fcode = *format++) {

                case '+':
                        flagword |= FPLUS;
                        goto charswitch;
                case '-':
                        flagword |= FMINUS;
                        flagword &= ~PADZERO; /* ignore 0 flag */
                        goto charswitch;
                case ' ':
                        flagword |= FBLANK;
                        goto charswitch;
                case '#':
                        flagword |= FSHARP;
                        goto charswitch;

                /* Scan the field width and precision */
                case '.':
                        flagword |= DOTSEEN;
                        prec = 0;
                        goto charswitch;

                case '*':
                        if (isdigit(*format)) {
                                starflg = 1;
                                bargs = args;
                                goto charswitch;
                        }
                        if (!(flagword & DOTSEEN)) {
                                width = va_arg(args.ap, int);
                                if (width < 0) {
                                        width = -width;
                                        flagword ^= FMINUS;
                                }
                        } else {
                                prec = va_arg(args.ap, int);
                                if (prec < 0)
                                        prec = 0;
                        }
                        goto charswitch;

                case '$':
                        {
                        int             position;
                        stva_list       targs;
                        if (fpos) {
                                _mkarglst(sformat, sargs, arglst);
                                fpos = 0;
                        }
                        if (flagword & DOTSEEN) {
                                position = prec;
                                prec = 0;
                        } else {
                                position = width;
                                width = 0;
                        }
                        if (position <= 0) {
                                /* illegal position */
                                format--;
                                continue;
                        }
                        if (position <= MAXARGS) {
                                targs = arglst[position - 1];
                        } else {
                                targs = arglst[MAXARGS - 1];
                                _getarg(sformat, &targs, position);
                        }
                        if (!starflg)
                                args = targs;
                        else {
                                starflg = 0;
                                args = bargs;
                                if (flagword & DOTSEEN)
                                        prec = va_arg(targs.ap, int);
                                else
                                        width = va_arg(targs.ap, int);
                        }
                        goto charswitch;
                        }

                case '0':       /* obsolescent spec:  leading zero in width */
                                /* means pad with leading zeros */
                        if (!(flagword & (DOTSEEN | FMINUS)))
                                flagword |= PADZERO;
                        /* FALLTHROUGH */
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                        {
                                int num = fcode - '0';
                                while (isdigit(fcode = *format)) {
                                        num = num * 10 + fcode - '0';
                                        format++;
                                }
                                if (flagword & DOTSEEN)
                                        prec = num;
                                else
                                        width = num;
                                goto charswitch;
                        }

                /* Scan the length modifier */
                case 'l':
                        flagword |= LENGTH;
                        goto charswitch;
                case 'h':
                        flagword |= SHORT;
                        goto charswitch;
                case 'L':
                        goto charswitch;

                /*
                 *      The character addressed by format must be
                 *      the format letter -- there is nothing
                 *      left for it to be.
                 *
                 *      The status of the +, -, #, and blank
                 *      flags are reflected in the variable
                 *      "flagword".  "width" and "prec" contain
                 *      numbers corresponding to the digit
                 *      strings before and after the decimal
                 *      point, respectively. If there was no
                 *      decimal point, then flagword & DOTSEEN
                 *      is false and the value of prec is meaningless.
                 *
                 *      The following switch cases set things up
                 *      for printing.  What ultimately gets
                 *      printed will be padding blanks, a
                 *      prefix, left padding zeroes, a value,
                 *      right padding zeroes, a suffix, and
                 *      more padding blanks.  Padding blanks
                 *      will not appear simultaneously on both
                 *      the left and the right.  Each case in
                 *      this switch will compute the value, and
                 *      leave in several variables the informa-
                 *      tion necessary to construct what is to
                 *      be printed.
                 *
                 *      The prefix is a sign, a blank, "0x",
                 *      "0X", or null, and is addressed by
                 *      "prefix".
                 *
                 *      The suffix is either null or an
                 *      exponent, and is addressed by "suffix".
                 *      If there is a suffix, the flagword bit
                 *      SUFFIX will be set.
                 *
                 *      The value to be printed starts at "bp"
                 *      and continues up to and not including
                 *      "p".
                 *
                 *      "lzero" and "rzero" will contain the
                 *      number of padding zeroes required on
                 *      the left and right, respectively.
                 *      The flagword bits LZERO and RZERO tell
                 *      whether padding zeros are required.
                 *
                 *      The number of padding blanks, and
                 *      whether they go on the left or the
                 *      right, will be computed on exit from
                 *      the switch.
                 */




                /*
                 *      decimal fixed point representations
                 *
                 *      HIBITL is 100...000
                 *      binary, and is equal to the maximum
                 *      negative number.
                 *      We assume a 2's complement machine
                 */

                case 'i':
                case 'd':
                        /* Fetch the argument to be printed */
                        if (flagword & LENGTH)
                                val = va_arg(args.ap, long);
                        else
                                val = va_arg(args.ap, int);

                        if (flagword & SHORT)
                                val = (short)val;

                        /* Set buffer pointer to last digit */
                        p = bp = buf + MAXDIGS;

                        /* If signed conversion, make sign */
                        if (val < 0) {
                                prefix = "-";
                                prefixlength = 1;
                                /*
                                 * Negate, checking in
                                 * advance for possible
                                 * overflow.
                                 */
                                if (val != HIBITL)
                                        val = -val;
                                else    /* number is -HIBITL; convert last */
                                        /* digit now and get positive number */
                                        *--bp = _lowdigit(&val);
                        } else if (flagword & FPLUS) {
                                prefix = "+";
                                prefixlength = 1;
                        } else if (flagword & FBLANK) {
                                prefix = " ";
                                prefixlength = 1;
                        }

                decimal:
                        {
                                long qval = val;
                                long saveq;

                                if (qval <= 9) {
                                        if (qval != 0 || !(flagword & DOTSEEN))
                                                *--bp = (char)(qval + '0');
                                } else {
                                        do {
                                                saveq = qval;
                                                qval /= 10;
                                                *--bp = (char)(saveq -
                                                    qval * 10 + '0');
                                        } while (qval > 9);
                                        *--bp = (char)(qval + '0');
                                        pdiff = (ptrdiff_t)saveq;
                                }
                        }

                        /* Calculate minimum padding zero requirement */
                        if (flagword & DOTSEEN) {
                                int leadzeroes = prec - (int)(p - bp);
                                if (leadzeroes > 0) {
                                        otherlength = lzero = leadzeroes;
                                        flagword |= LZERO;
                                }
                        }

                        break;

                case 'u':
                        /* Fetch the argument to be printed */
                        if (flagword & LENGTH)
                                val = va_arg(args.ap, long);
                        else
                                val = va_arg(args.ap, unsigned);

                        if (flagword & SHORT)
                                val = (unsigned short)val;

                        p = bp = buf + MAXDIGS;

                        if (val & HIBITL)
                                *--bp = _lowdigit(&val);

                        goto decimal;

                /*
                 *      non-decimal fixed point representations
                 *      for radix equal to a power of two
                 *
                 *      "mradix" is one less than the radix for the conversion.
                 *      "lradix" is one less than the base 2 log
                 *      of the radix for the conversion. Conversion is unsigned.
                 *      HIBITL is 100...000
                 *      binary, and is equal to the maximum
                 *      negative number.
                 *      We assume a 2's complement machine
                 */

                case 'o':
                        mradix = 7;
                        lradix = 2;
                        goto fixed;

                case 'X':
                case 'x':
                case 'p':
                        mradix = 15;
                        lradix = 3;

                fixed:
                        /* Fetch the argument to be printed */
                        if (flagword & LENGTH)
                                val = va_arg(args.ap, long);
                        else
                                val = va_arg(args.ap, unsigned);

                        if (flagword & SHORT)
                                val = (unsigned short)val;

                        /* Set translate table for digits */
                        tab = (fcode == 'X') ? uc_digs : lc_digs;

                        /* Entry point when printing a double which is a NaN */
                put_pc:
                        /* Develop the digits of the value */
                        p = bp = buf + MAXDIGS;
                        {
                                long qval = val;
                                if (qval == 0) {
                                        if (!(flagword & DOTSEEN)) {
                                                otherlength = lzero = 1;
                                                flagword |= LZERO;
                                        }
                                } else
                                        do {
                                                *--bp = tab[qval & mradix];
                                                qval = ((qval >> 1) & ~HIBITL)
                                                    >> lradix;
                                        } while (qval != 0);
                        }

                        /* Calculate minimum padding zero requirement */
                        if (flagword & DOTSEEN) {
                                int leadzeroes = prec - (int)(p - bp);
                                if (leadzeroes > 0) {
                                        otherlength = lzero = leadzeroes;
                                        flagword |= LZERO;
                                }
                        }

                        /* Handle the # flag */
                        if (flagword & FSHARP && val != 0)
                                switch (fcode) {
                                case 'o':
                                        if (!(flagword & LZERO)) {
                                                otherlength = lzero = 1;
                                                flagword |= LZERO;
                                        }
                                        break;
                                case 'x':
                                        prefix = "0x";
                                        prefixlength = 2;
                                        break;
                                case 'X':
                                        prefix = "0X";
                                        prefixlength = 2;
                                        break;
                                }

                        break;

                case 'E':
                case 'e':
                        /*
                         * E-format.  The general strategy
                         * here is fairly easy: we take
                         * what ecvt gives us and re-format it.
                         */

                        /* Establish default precision */
                        if (!(flagword & DOTSEEN))
                                prec = 6;

                        /* Fetch the value */
                        dval = va_arg(args.ap, double);

                        /* Check for NaNs and Infinities */
                        if (IsNANorINF(dval)) {
                                if (IsINF(dval)) {
                                        if (IsNegNAN(dval))
                                                neg_in = 1;
                                        inf_nan = 1;
                                        bp = (fcode == 'E')? uc_inf: lc_inf;
                                        p = bp + 3;
                                        break;
                                } else {
                                        if (IsNegNAN(dval))
                                                neg_in = 1;
                                        inf_nan = 1;
                                        val = GETNaNPC(dval);
                                        NaN_flg = SNLEN;
                                        mradix = 15;
                                        lradix = 3;
                                        if (fcode == 'E') {
                                                SNAN = uc_nan;
                                                tab =  uc_digs;
                                        } else {
                                                SNAN =  lc_nan;
                                                tab =  lc_digs;
                                        }
                                        goto put_pc;
                                }
                        }
                        /* Develop the mantissa */
                        bp = ecvt(dval, min(prec + 1, MAXECVT), &decpt, &sign);

                        /* Determine the prefix */
                e_merge:
                        if (sign) {
                                prefix = "-";
                                prefixlength = 1;
                        } else if (flagword & FPLUS) {
                                prefix = "+";
                                prefixlength = 1;
                        } else if (flagword & FBLANK) {
                                prefix = " ";
                                prefixlength = 1;
                        }

                        /* Place the first digit in the buffer */
                        p = &buf[0];
                        *p++ = (*bp != '\0') ? *bp++ : '0';

                        /* Put in a decimal point if needed */
                        if (prec != 0 || (flagword & FSHARP))
                                *p++ = _numeric[0];

                        /* Create the rest of the mantissa */
                        {
                                int rz = prec;
                                for (; rz > 0 && *bp != '\0'; --rz)
                                        *p++ = *bp++;
                                if (rz > 0) {
                                        otherlength = rzero = rz;
                                        flagword |= RZERO;
                                }
                        }

                        bp = &buf[0];

                        /* Create the exponent */
                        *(suffix = &expbuf[MAXESIZ]) = '\0';
                        if (dval != 0) {
                                int nn = decpt - 1;
                                if (nn < 0)
                                        nn = -nn;
                                for (; nn > 9; nn /= 10)
                                        *--suffix = todigit(nn % 10);
                                *--suffix = todigit(nn);
                        }

                        /* Prepend leading zeroes to the exponent */
                        while (suffix > &expbuf[MAXESIZ - 2])
                                *--suffix = '0';

                        /* Put in the exponent sign */
                        *--suffix = (decpt > 0 || dval == 0) ? '+' : '-';

                        /* Put in the e */
                        *--suffix = isupper(fcode) ? 'E'  : 'e';

                        /* compute size of suffix */
                        otherlength += (suffixlength =
                            (int)(&expbuf[MAXESIZ] - suffix));
                        flagword |= SUFFIX;

                        break;

                case 'f':
                        /*
                         * F-format floating point.  This is a
                         * good deal less simple than E-format.
                         * The overall strategy will be to call
                         * fcvt, reformat its result into buf,
                         * and calculate how many trailing
                         * zeroes will be required.  There will
                         * never be any leading zeroes needed.
                         */

                        /* Establish default precision */
                        if (!(flagword & DOTSEEN))
                                prec = 6;

                        /* Fetch the value */
                        dval = va_arg(args.ap, double);

                        /* Check for NaNs and Infinities  */
                        if (IsNANorINF(dval)) {
                                if (IsINF(dval)) {
                                        if (IsNegNAN(dval))
                                                neg_in = 1;
                                        inf_nan = 1;
                                        bp = lc_inf;
                                        p = bp + 3;
                                        break;
                                } else {
                                        if (IsNegNAN(dval))
                                                neg_in = 1;
                                        inf_nan = 1;
                                        val  = GETNaNPC(dval);
                                        NaN_flg = SNLEN;
                                        mradix = 15;
                                        lradix = 3;
                                        tab =  lc_digs;
                                        SNAN = lc_nan;
                                        goto put_pc;
                                }
                        }
                        /* Do the conversion */
                        bp = fcvt(dval, min(prec, MAXFCVT), &decpt, &sign);

                        /* Determine the prefix */
                f_merge:
                        if (sign) {
                                prefix = "-";
                                prefixlength = 1;
                        } else if (flagword & FPLUS) {
                                prefix = "+";
                                prefixlength = 1;
                        } else if (flagword & FBLANK) {
                                prefix = " ";
                                prefixlength = 1;
                        }

                        /* Initialize buffer pointer */
                        p = &buf[0];
                        {
                                int nn = decpt;

                                /* Emit the digits before the decimal point */
                                k = 0;
                                do {
                                        *p++ = (nn <= 0 || *bp == '\0' ||
                                            k >= MAXFSIG) ?
                                            '0' : (k++, *bp++);
                                } while (--nn > 0);

                                /* Decide whether we need a decimal point */
                                if ((flagword & FSHARP) || prec > 0)
                                        *p++ = _numeric[0];

                                /* Digits (if any) after the decimal point */
                                nn = min(prec, MAXFCVT);
                                if (prec > nn) {
                                        flagword |= RZERO;
                                        otherlength = rzero = prec - nn;
                                }
                                while (--nn >= 0)
                                        *p++ = (++decpt <= 0 || *bp == '\0' ||
                                            k >= MAXFSIG) ?
                                            '0' : (k++, *bp++);
                        }

                        bp = &buf[0];

                        break;

                case 'G':
                case 'g':
                        /*
                         * g-format.  We play around a bit
                         * and then jump into e or f, as needed.
                         */

                        /* Establish default precision */
                        if (!(flagword & DOTSEEN))
                                prec = 6;
                        else if (prec == 0)
                                prec = 1;

                        /* Fetch the value */
                        dval = va_arg(args.ap, double);

                        /* Check for NaN and Infinities  */
                        if (IsNANorINF(dval)) {
                                if (IsINF(dval)) {
                                        if (IsNegNAN(dval))
                                                neg_in = 1;
                                        bp = (fcode == 'G') ? uc_inf : lc_inf;
                                        p = bp + 3;
                                        inf_nan = 1;
                                        break;
                                } else {
                                        if (IsNegNAN(dval))
                                                neg_in = 1;
                                        inf_nan = 1;
                                        val  = GETNaNPC(dval);
                                        NaN_flg = SNLEN;
                                        mradix = 15;
                                        lradix = 3;
                                        if (fcode == 'G') {
                                                SNAN = uc_nan;
                                                tab = uc_digs;
                                        } else {
                                                SNAN = lc_nan;
                                                tab =  lc_digs;
                                        }
                                        goto put_pc;
                                }
                        }

                        /* Do the conversion */
                        bp = ecvt(dval, min(prec, MAXECVT), &decpt, &sign);
                        if (dval == 0)
                                decpt = 1;
                        {
                                int kk = prec;
                                size_t sz;

                                if (!(flagword & FSHARP)) {
                                        sz = strlen(bp);
                                        if (sz < kk)
                                                kk = (int)sz;
                                        while (kk >= 1 && bp[kk-1] == '0')
                                                --kk;
                                }

                                if (decpt < -3 || decpt > prec) {
                                        prec = kk - 1;
                                        goto e_merge;
                                }
                                prec = kk - decpt;
                                goto f_merge;
                        }

                case '%':
                        buf[0] = (char)fcode;
                        goto c_merge;

                case 'c':
                        buf[0] = va_arg(args.ap, int);
                c_merge:
                        p = (bp = &buf[0]) + 1;
                        break;

                case 's':
                        bp = va_arg(args.ap, char *);
                        if (!(flagword & DOTSEEN))
                                p = bp + strlen(bp);
                        else { /* a strnlen function would  be useful here! */
                                char *qp = bp;
                                while (*qp++ != '\0' && --prec >= 0)
                                        ;
                                p = qp - 1;
                        }
                        break;

                case 'n':
                        {
                                if (flagword & LENGTH) {
                                        long *svcount;
                                        svcount = va_arg(args.ap, long *);
                                        *svcount = count;
                                } else if (flagword & SHORT) {
                                        short *svcount;
                                        svcount = va_arg(args.ap, short *);
                                        *svcount = (short)count;
                                } else {
                                        int *svcount;
                                        svcount = va_arg(args.ap, int *);
                                        *svcount = count;
                                }
                                continue;
                        }

                default: /* this is technically an error; what we do is to */
                        /* back up the format pointer to the offending char */
                        /* and continue with the format scan */
                        format--;
                        continue;

                }

                if (inf_nan) {
                        if (neg_in) {
                                prefix = "-";
                                prefixlength = 1;
                                neg_in = 0;
                        } else if (flagword & FPLUS) {
                                prefix = "+";
                                prefixlength = 1;
                        } else if (flagword & FBLANK) {
                                prefix = " ";
                                prefixlength = 1;
                        }
                        inf_nan = 0;
                }

                /* Calculate number of padding blanks */
                k = (int)(pdiff = p - bp) + prefixlength + otherlength +
                    NaN_flg;
                if (width <= k)
                        count += k;
                else {
                        count += width;

                        /* Set up for padding zeroes if requested */
                        /* Otherwise emit padding blanks unless output is */
                        /* to be left-justified.  */

                        if (flagword & PADZERO) {
                                if (!(flagword & LZERO)) {
                                        flagword |= LZERO;
                                        lzero = width - k;
                                }
                                else
                                        lzero += width - k;
                                k = width; /* cancel padding blanks */
                        } else
                                /* Blanks on left if required */
                                if (!(flagword & FMINUS))
                                        PAD(_blanks, width - k);
                }

                /* Prefix, if any */
                if (prefixlength != 0)
                        PUT(prefix, prefixlength);

                /* If value is NaN, put string NaN */
                if (NaN_flg) {
                        PUT(SNAN, SNLEN);
                        NaN_flg = 0;
                }

                /* Zeroes on the left */
                if (flagword & LZERO)
                        PAD(_zeroes, lzero);

                /* The value itself */
                if (pdiff > 0)
                        PUT(bp, pdiff);

                if (flagword & (RZERO | SUFFIX | FMINUS)) {
                        /* Zeroes on the right */
                        if (flagword & RZERO)
                                PAD(_zeroes, rzero);

                        /* The suffix */
                        if (flagword & SUFFIX)
                                PUT(suffix, suffixlength);

                        /* Blanks on the right if required */
                        if (flagword & FMINUS && width > k)
                                PAD(_blanks, width - k);
                }
        }
}

/*
 * This function initializes arglst, to contain the appropriate va_list values
 * for the first MAXARGS arguments.
 */
void
_mkarglst(char *fmt, stva_list args, stva_list arglst[])
{
        static char digits[] = "01234567890", skips[] = "# +-.0123456789hL$";

        enum types {INT = 1, LONG, CHAR_PTR, DOUBLE, LONG_DOUBLE, VOID_PTR,
                LONG_PTR, INT_PTR};
        enum types typelst[MAXARGS], curtype;
        int maxnum, n, curargno, flags;

        /*
         * Algorithm    1. set all argument types to zero.
         *              2. walk through fmt putting arg types in typelst[].
         *              3. walk through args using va_arg(args.ap, typelst[n])
         *                 and set arglst[] to the appropriate values.
         * Assumptions: Cannot use %*$... to specify variable position.
         */

        (void) memset((void *)typelst, 0, sizeof (typelst));
        maxnum = -1;
        curargno = 0;
        while ((fmt = strchr(fmt, '%')) != 0) {
                size_t sz;

                fmt++;  /* skip % */
                if (fmt[sz = strspn(fmt, digits)] == '$') {
                        curargno = atoi(fmt) - 1;
                                /* convert to zero base */
                        if (curargno < 0)
                                continue;
                        fmt += sz + 1;
                }
                flags = 0;
        again:;
                fmt += strspn(fmt, skips);
                switch (*fmt++) {
                case '%':       /* there is no argument! */
                        continue;
                case 'l':
                        flags |= 0x1;
                        goto again;
                case '*':       /* int argument used for value */
                        /* check if there is a positional parameter */
                        if (isdigit(*fmt)) {
                                int     targno;
                                targno = atoi(fmt) - 1;
                                fmt += strspn(fmt, digits);
                                if (*fmt == '$')
                                        fmt++; /* skip '$' */
                                if (targno >= 0 && targno < MAXARGS) {
                                        typelst[targno] = INT;
                                        if (maxnum < targno)
                                                maxnum = targno;
                                }
                                goto again;
                        }
                        flags |= 0x2;
                        curtype = INT;
                        break;
                case 'e':
                case 'E':
                case 'f':
                case 'g':
                case 'G':
                        curtype = DOUBLE;
                        break;
                case 's':
                        curtype = CHAR_PTR;
                        break;
                case 'p':
                        curtype = VOID_PTR;
                        break;
                case 'n':
                        if (flags & 0x1)
                                curtype = LONG_PTR;
                        else
                                curtype = INT_PTR;
                        break;
                default:
                        if (flags & 0x1)
                                curtype = LONG;
                        else
                                curtype = INT;
                        break;
                }
                if (curargno >= 0 && curargno < MAXARGS) {
                        typelst[curargno] = curtype;
                        if (maxnum < curargno)
                                maxnum = curargno;
                }
                curargno++;     /* default to next in list */
                if (flags & 0x2)        /* took care of *, keep going */
                {
                        flags ^= 0x2;
                        goto again;
                }
        }
        for (n = 0; n <= maxnum; n++) {
                arglst[n] = args;
                if (typelst[n] == 0)
                        typelst[n] = INT;

                switch (typelst[n]) {
                case INT:
                        (void) va_arg(args.ap, int);
                        break;
                case LONG:
                        (void) va_arg(args.ap, long);
                        break;
                case CHAR_PTR:
                        (void) va_arg(args.ap, char *);
                        break;
                case DOUBLE:
                        (void) va_arg(args.ap, double);
                        break;
                case LONG_DOUBLE:
                        (void) va_arg(args.ap, double);
                        break;
                case VOID_PTR:
                        (void) va_arg(args.ap, void *);
                        break;
                case LONG_PTR:
                        (void) va_arg(args.ap, long *);
                        break;
                case INT_PTR:
                        (void) va_arg(args.ap, int *);
                        break;
                }
        }
}

/*
 * This function is used to find the va_list value for arguments whose
 * position is greater than MAXARGS.  This function is slow, so hopefully
 * MAXARGS will be big enough so that this function need only be called in
 * unusual circumstances.
 * pargs is assumed to contain the value of arglst[MAXARGS - 1].
 */
void
_getarg(char *fmt, stva_list *pargs, int argno)
{
        static char digits[] = "01234567890", skips[] = "# +-.0123456789h$";
        int i, curargno, flags;
        size_t n;
        char    *sfmt = fmt;
        int     found = 1;

        i = MAXARGS;
        curargno = 1;
        while (found) {
                fmt = sfmt;
                found = 0;
                while ((i != argno) && (fmt = strchr(fmt, '%')) != 0) {
                        fmt++;  /* skip % */
                        if (fmt[n = strspn(fmt, digits)] == '$') {
                                curargno = atoi(fmt);
                                if (curargno <= 0)
                                        continue;
                                fmt += n + 1;
                        }

                        /* find conversion specifier for next argument */
                        if (i != curargno) {
                                curargno++;
                                continue;
                        } else
                                found = 1;
                        flags = 0;
                again:;
                        fmt += strspn(fmt, skips);
                        switch (*fmt++) {
                        case '%':       /* there is no argument! */
                                continue;
                        case 'l':
                                flags |= 0x1;
                                goto again;
                        case '*':       /* int argument used for value */
                                /*
                                 * check if there is a positional parameter;
                                 * if so, just skip it; its size will be
                                 * correctly determined by default
                                 */
                                if (isdigit(*fmt)) {
                                        fmt += strspn(fmt, digits);
                                        if (*fmt == '$')
                                                fmt++; /* skip '$' */
                                        goto again;
                                }
                                flags |= 0x2;
                                (void) va_arg((*pargs).ap, int);
                                break;
                        case 'e':
                        case 'E':
                        case 'f':
                        case 'g':
                        case 'G':
                                if (flags & 0x1)
                                        (void) va_arg((*pargs).ap, double);
                                else
                                        (void) va_arg((*pargs).ap, double);
                                break;
                        case 's':
                                (void) va_arg((*pargs).ap, char *);
                                break;
                        case 'p':
                                (void) va_arg((*pargs).ap, void *);
                                break;
                        case 'n':
                                if (flags & 0x1)
                                        (void) va_arg((*pargs).ap, long *);
                                else
                                        (void) va_arg((*pargs).ap, int *);
                                break;
                        default:
                                if (flags & 0x1)
                                        (void) va_arg((*pargs).ap, long int);
                                else
                                        (void) va_arg((*pargs).ap, int);
                                break;
                        }
                        i++;
                        curargno++;     /* default to next in list */
                        if (flags & 0x2)        /* took care of *, keep going */
                        {
                                flags ^= 0x2;
                                goto again;
                        }
                }

                /*
                 * missing specifier for parameter, assume parameter is an int
                 */
                if (!found && i != argno) {
                        (void) va_arg((*pargs).ap, int);
                        i++;
                        curargno = i;
                        found = 1;
                }
        }
}