OpenSolaris_b135/cmd/csh/printf.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

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