root/usr/src/cmd/csh/printf.c
/*
 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

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

/*
 * Copyright (c) 1980 Regents of the University of California.
 * All rights reserved. The Berkeley Software License Agreement
 * specifies the terms and conditions for redistribution.
 */

/*
 * Hacked "printf" which prints through putbyte and Putchar.
 * putbyte() is used to send a pure byte, which might be a part
 * of a mutlibyte character, mainly for %s.  A control character
 * for putbyte() may be QUOTE'd meaning not to convert it to ^x
 * sequence.  In all other cases Putchar() is used to send a character
 * in tchar (== wchar_t + * optional QUOE.)
 * DONT USE WITH STDIO!
 * This printf has been hacked again so that it understands tchar string
 * when the format specifier %t is used.  Also %c has been expanded
 * to take a tchar character as well as normal int.
 * %t is supported in its simplest form; no width or precision will
 * be understood.
 * Assumption here is that sizeof(tchar)<=sizeof(int) so that tchar is
 * passed as int.  Otherwise, %T must be specified instead of %c to
 * print a character in tchar.
 */

#include <stdarg.h>
#include <values.h>
#include "sh.h" /* For tchar. */

#define HIBITLL         (1ULL << 63)

void _print(char *format, va_list *args);

static char *p;

int
printf(const char *format, ...)
{
        va_list stupid;

        p = (char *)gettext(format);
        va_start(stupid, format);
        _print(p, &stupid);
        va_end(stupid);

        return (0);
}

/*
 *      Floating-point code is included or not, depending
 *      on whether the preprocessor variable FLOAT is 1 or 0.
 */

/* Maximum number of digits in any integer (long) representation */
#define MAXDIGS 20

/* Convert a digit character to the corresponding number */
#define tonumber(x)     ((x) - '0')

/* Convert a number between 0 and 9 to the corresponding digit */
#define todigit(x)      ((x) + '0')

/* Maximum total number of digits in E format */
#define MAXECVT 17

/* Maximum number of digits after decimal point in F format */
#define MAXFCVT 60

/* Maximum significant figures in a floating-point number */
#define MAXFSIG 17

/* Maximum number of characters in an exponent */
#define MAXESIZ 4

/* Maximum (positive) exponent or greater */
#define MAXEXP  40



#define max(a, b) ((a) > (b) ? (a) : (b))
#define min(a, b) ((a) < (b) ? (a) : (b))

/* If this symbol is nonzero, allow '0' as a flag */
#define FZERO 1

#if FLOAT
/*
 *      System-supplied routines for floating conversion
 */
char *fcvt();
char *ecvt();
#endif

void
_print(char *format, va_list *args)
{
        /* Current position in format */
        char *cp;

        /* Starting and ending points for value to be printed */
        char *bp, *p;
        tchar *tbp, *tep;       /* For "%t". */
        tchar tcbuf[2];         /* For "%c" or "%T". */

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

        /* Format code */
        char fcode;

        /* Number of padding zeroes required on the left */
        int lzero;

        /* Flags - nonzero if corresponding character appears in format */
        bool length;            /* l */
        bool double_length;     /* ll */
        bool fplus;             /* + */
        bool fminus;            /* - */
        bool fblank;            /* blank */
        bool fsharp;            /* # */
#if FZERO
        bool fzero;             /* 0 */
#endif

        /* Pointer to sign, "0x", "0X", or empty */
        char *prefix;
#if FLOAT
        /* Exponent or empty */
        char *suffix;

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

        /* Number of padding zeroes required on the right */
        int rzero;

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

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

        /* Scratch */
        int k;

        /* Values are developed in this buffer */
        char buf[max(MAXDIGS, max(MAXFCVT + DMAXEXP, MAXECVT) + 1)];
#else
        char buf[MAXDIGS];
#endif
        /* The value being converted, if integer */
        long long val;

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

        /* Work variables */
        int n, hradix, lowbit;

        cp = format;

        /*
         *      The main loop -- this loop goes through one iteration
         *      for each ordinary character or format specification.
         */
        while (*cp)
                if (*cp != '%') {
                        /* Ordinary (non-%) character */
                        putbyte (*cp++);
                } else {
                        /*
                         *      % has been found.
                         *      First, parse the format specification.
                         */

                        /* Scan the <flags> */
                        fplus = fminus = fblank = fsharp = 0;
#if FZERO
                        fzero = 0;
#endif
scan:
                        switch (*++cp) {
                        case '+':
                                fplus = 1;
                                goto scan;
                        case '-':
                                fminus = 1;
                                goto scan;
                        case ' ':
                                fblank = 1;
                                goto scan;
                        case '#':
                                fsharp = 1;
                                goto scan;
#if FZERO
                        case '0':
                                fzero = 1;
                                goto scan;
#endif
                        }

                        /* Scan the field width */
                        if (*cp == '*') {
                                width = va_arg (*args, int);
                                if (width < 0) {
                                        width = -width;
                                        fminus = 1;
                                }
                                cp++;
                        } else {
                                width = 0;
                                while (isdigit(*cp)) {
                                        n = tonumber(*cp++);
                                        width = width * 10 + n;
                                }
                        }

                        /* Scan the precision */
                        if (*cp == '.') {

                                /* '*' instead of digits? */
                                if (*++cp == '*') {
                                        prec = va_arg(*args, int);
                                        cp++;
                                } else {
                                        prec = 0;
                                        while (isdigit(*cp)) {
                                                n = tonumber(*cp++);
                                                prec = prec * 10 + n;
                                        }
                                }
                        } else {
                                prec = -1;
                        }

                        /* Scan the length modifier */
                        double_length = length = 0;
                        switch (*cp) {
                        case 'l':
                                if (*(cp + 1) == 'l') {
                                        cp++;
                                        double_length = 1;
                                } else {
                                        length = 1;
                                }
                                /* No break */
                        case 'h':
                                cp++;
                        }

                        /*
                         *      The character addressed by cp must be the
                         *      format letter -- there is nothing left for
                         *      it to be.
                         *
                         *      The status of the +, -, #, blank, and 0
                         *      flags are reflected in the variables
                         *      "fplus", "fminus", "fsharp", "fblank",
                         *      and "fzero", respectively.
                         *      "width" and "prec" contain numbers
                         *      corresponding to the digit strings
                         *      before and after the decimal point,
                         *      respectively. If there was no decimal
                         *      point, "prec" is -1.
                         *
                         *      The following switch sets 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 information 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".
                         *
                         *      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.  If either of
                         *      these variables is negative, it will be
                         *      treated as if it were zero.
                         *
                         *      The number of padding blanks, and whether
                         *      they go on the left or the right, will be
                         *      computed on exit from the switch.
                         */

                        lzero = 0;
                        prefix = "";
#if FLOAT
                        rzero = lzero;
                        suffix = prefix;
#endif
                        switch (fcode = *cp++) {

                        /*
                         *      fixed point representations
                         *
                         *      "hradix" is half the radix for the conversion.
                         *      Conversion is unsigned unless fcode is 'd'.
                         *      HIBITLL is 1000...000 binary, and is equal to
                         *              the maximum negative number.
                         *      We assume a 2's complement machine
                         */

                        case 'D':
                        case 'U':
                                length = 1;
                        case 'd':
                        case 'u':
                                hradix = 5;
                                goto fixed;

                        case 'O':
                                length = 1;
                        case 'o':
                                hradix = 4;
                                goto fixed;

                        case 'X':
                        case 'x':
                                hradix = 8;

fixed:
                                /* Establish default precision */
                                if (prec < 0) {
                                        prec = 1;
                                }

                                /* Fetch the argument to be printed */
                                if (double_length) {
                                        val = va_arg(*args, long long);
                                } else if (length) {
                                        val = va_arg(*args, long);
                                } else if (fcode == 'd') {
                                        val = va_arg(*args, int);
                                } else {
                                        val = va_arg(*args, unsigned);
                                }

                                /* If signed conversion, establish sign */
                                if (fcode == 'd' || fcode == 'D') {
                                        if (val < 0) {
                                                prefix = "-";
                                                /*
                                                 *      Negate, checking in
                                                 *      advance for possible
                                                 *      overflow.
                                                 */
                                                if (val != HIBITLL) {
                                                        val = -val;
                                                }
                                        } else if (fplus) {
                                                prefix = "+";
                                        } else if (fblank) {
                                                prefix = " ";
                                        }
                                }
#if FZERO
                                if (fzero) {
                                        int n = width - strlen(prefix);
                                        if (n > prec) {
                                                prec = n;
                                        }
                                }
#endif
                                /* Set translate table for digits */
                                if (fcode == 'X') {
                                        tab = "0123456789ABCDEF";
                                } else {
                                        tab = "0123456789abcdef";
                                }

                                /* Develop the digits of the value */
                                p = bp = buf + MAXDIGS;
                                while (val) {
                                        lowbit = val & 1;
                                        val = (val >> 1) & ~HIBITLL;
                                        *--bp = tab[val % hradix * 2 + lowbit];
                                        val /= hradix;
                                }

                                /* Calculate padding zero requirement */
                                lzero = bp - p + prec;

                                /* Handle the # flag */
                                if (fsharp && bp != p) {
                                        switch (fcode) {
                                        case 'o':
                                                if (lzero < 1)
                                                        lzero = 1;
                                                break;
                                        case 'x':
                                                prefix = "0x";
                                                break;
                                        case 'X':
                                                prefix = "0X";
                                                break;
                                        }
                                }

                                break;
#if FLOAT
                        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 (prec < 0) {
                                        prec = 6;
                                }

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

                                /* Develop the mantissa */
                                bp = ecvt(dval,
                                    min(prec + 1, MAXECVT),
                                    &decpt,
                                    &sign);

                                /* Determine the prefix */
e_merge:
                                if (sign) {
                                        prefix = "-";
                                } else if (fplus) {
                                        prefix = "+";
                                } else if (fblank) {
                                        prefix = " ";
                                }

                                /* 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 || fsharp) {
                                        *p++ = '.';
                                }

                                /* Create the rest of the mantissa */
                                rzero = prec;
                                while (rzero > 0 && *bp != '\0') {
                                        --rzero;
                                        *p++ = *bp++;
                                }

                                bp = &buf[0];

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

                                /* 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';

                                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 (prec < 0) {
                                        prec = 6;
                                }

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

                                /* Do the conversion */
                                bp = fcvt(dval,
                                    min(prec, MAXFCVT),
                                    &decpt,
                                    &sign);

                                /* Determine the prefix */
f_merge:
                                if (sign && decpt > -prec &&
                                    *bp != '\0' && *bp != '0') {
                                        prefix = "-";
                                } else if (fplus) {
                                        prefix = "+";
                                } else if (fblank) {
                                        prefix = " ";
                                }

                                /* Initialize buffer pointer */
                                p = &buf[0];

                                /* Emit the digits before the decimal point */
                                n = decpt;
                                k = 0;
                                if (n <= 0) {
                                        *p++ = '0';
                                } else {
                                        do {
                                                if (*bp == '\0' ||
                                                    k >= MAXFSIG) {
                                                        *p++ = '0';
                                                } else {
                                                        *p++ = *bp++;
                                                        ++k;
                                                }
                                        } while (--n != 0);
                                }

                                /* Decide whether we need a decimal point */
                                if (fsharp || prec > 0) {
                                        *p++ = '.';
                                }

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

                                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 (prec < 0) {
                                        prec = 6;
                                }

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

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

                                k = prec;
                                if (!fsharp) {
                                        n = strlen(bp);
                                        if (n < k) {
                                                k = n;
                                        }
                                        while (k >= 1 && bp[k-1] == '0') {
                                                --k;
                                        }
                                }

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

#endif
                        case 'c':
#ifdef MBCHAR_1 /* sizeof(int)>=sizeof(tchar) */
/*
 * A tchar arg is passed as int so we used the normal %c to specify
 * such an arugument.
 */
                                tcbuf[0] = va_arg(*args, int);
                                tbp = &tcbuf[0];
                                tep = tbp + 1;
                                fcode = 't'; /* Fake the rest of code. */
                                break;
#else
/*
 * We would have to invent another new format speficier such as "%T" to
 * take a tchar arg.  Let's worry about when that time comes.
 */
                                /*
                                 * Following code take care of a char arg
                                 * only.
                                 */
                                buf[0] = va_arg(*args, int);
                                bp = &buf[0];
                                p = bp + 1;
                                break;
                        case 'T': /* Corresponding arg is tchar. */
                                tcbuf[0] = va_arg(*args, tchar);
                                tbp = &tcbuf[0];
                                tep = tbp + 1;
                                fcode = 't'; /* Fake the rest of code. */
                                break;
#endif
                        case 's':
                                bp = va_arg(*args, char *);
                                if (bp == 0) {
nullstr:                                bp = "(null)";
                                        p = bp + strlen("(null)");
                                        break;
                                }
                                if (prec < 0) {
                                        prec = MAXINT;
                                }
                                for (n = 0; *bp++ != '\0' && n < prec; n++)
                                        ;
                                p = --bp;
                                bp -= n;
                                break;

                        case 't':
                                /*
                                 * Special format specifier "%t" tells
                                 * printf() to print char strings written
                                 * as tchar string.
                                 */
                                tbp = va_arg(*args, tchar *);
                                if (tbp == 0) {
                                        fcode = 's'; /* Act as if it were %s. */
                                        goto nullstr;
                                }
                                if (prec < 0) {
                                        prec = MAXINT;
                                }
                                for (n = 0; *tbp++ != 0 && n < prec; n++)
                                        ;
                                tep = --tbp;
                                tbp -= n;

                                /*
                                 * Just to make the following padding
                                 * calculation not to go very crazy...
                                 */
                                bp = NULL;
                                p = bp + n;
                                break;

                        case '\0':
                                cp--;
                                break;

                        default:
                                p = bp = &fcode;
                                p++;
                                break;

                        }
                        if (fcode != '\0') {
                                /* Calculate number of padding blanks */
                                int nblank;
                                nblank = width
#if FLOAT
                                        - (rzero < 0 ? 0:  rzero)
                                        - strlen(suffix)
#endif
                                        - (p - bp)
                                        - (lzero < 0 ? 0 : lzero)
                                        - strlen(prefix);

                                /* Blanks on left if required */
                                if (!fminus) {
                                        while (--nblank >= 0) {
                                                Putchar(' ');
                                        }
                                }

                                /* Prefix, if any */
                                while (*prefix != '\0') {
                                        Putchar(*prefix++);
                                }

                                /* Zeroes on the left */
                                while (--lzero >= 0) {
                                        Putchar('0');
                                }

                                /* The value itself */
                                if (fcode == 't') {     /* %t is special. */
                                        while (tbp < tep) {
                                            Putchar(*tbp++);
                                        }
                                } else {        /* For rest of the cases. */
                                        while (bp < p) {
                                            putbyte(*bp++);
                                        }
                                }
#if FLOAT
                                /* Zeroes on the right */
                                while (--rzero >= 0)
                                        Putchar('0');

                                /* The suffix */
                                while (*suffix != '\0') {
                                        Putchar(*suffix++);
                                }
#endif
                                /* Blanks on the right if required */
                                if (fminus) {
                                        while (--nblank >= 0) {
                                                Putchar(' ');
                                        }
                                }
                        }
                }
}