/* #define	TESTING
 */
/*
 *			d o p r n t . c
 *
 */

/*)LIBRARY
*/

#ifdef	DOCUMENTATION

title		Output Formatter
index		Output formatter

internal

synopsis
	.s.nf
	c_doprnt(format, argvec, iov)
	char		*format;
	int		*argvec[];
	FILE		*iov;
	.s.f
Description

	c_doprnt() performs all formatting operations for printf(),
	sprintf(), and fprintf().  The formatting options are described
	in the documentation of printf().

Bugs

	c_doprnt() is functionally identical to the _doprnt() module
	in Unix and Vax-11 C libraries.  Unfortunately, the initial '_'
	conflicts with RSX FCS library conventions.

Internal

	A test routine may be conditionally compiled.

#endif

#include		<stdio.h>
#include		<ctype.h>
#define	EOS		('\0')
#define	FALSE		0
#define	TRUE		1
#ifdef	PDP10
#define	BITS_PER_CHAR	36
#else
#define	BITS_PER_CHAR	8
#endif

/*
 * WORKSIZE is the dimension of work[] which must hold enough characters
 * to store a long integer (expanded using the %b format) + '-' and EOS
 */
#define	WORKSIZE	(((sizeof (long)) * BITS_PER_CHAR) + 2)

struct	fmtbuf {
	char	f_type;		/* Format type ('d', etc.)		*/
	char	f_width;	/* Argument width (INT or LONG)		*/
	char	f_radix;	/* Argument radix (8, 10, etc.)		*/
};

#define	INT	0
#define	LONG	1
#define	NEG	2		/* Signal for output of '-'		*/

#ifdef	M68000
ROM_SECT(rom00);
#endif
/*
 * formatinfo[] stores information about the "value" formats
 */
static struct fmtbuf formatinfo[] = {
	{ 'b',	INT,	 2 },
	{ 'd',	INT,	10 },
	{ 'o',	INT,	 8 },
	{ 'u',	INT,	10 },
	{ 'x',	INT,	16 },
	{ 'B',	LONG,	 2 },
	{ 'D',	LONG,	10 },
	{ 'O',	LONG,	 8 },
	{ 'U',	LONG,	10 },
	{ 'X',	LONG,	16 },
	{ EOS,	0,	 0 },
};


c_doprnt(format, argp, fildes)
char		*format;		/* Format string		*/
register int	*argp;			/* Argument vector pointer	*/
FILE		*fildes;		/* File descriptor		*/
{
	register int		c;
	register union {
	    struct fmtbuf	*fmt;
	    char		*result;
	} p;		
	char		ljust;		/* TRUE for left-justification	*/
	char		work[WORKSIZE];	/* Number buffer		*/
	char		fill;		/* '0' or ' ' for fill		*/
	int		prec;		/* Precision			*/
	int		slen;		/* Field length			*/
	int		width;		/* Argument width		*/
#ifdef	decus
	unsigned	value;		/* Current argument value	*/
#else
	unsigned long	value;
#endif
	int		temp;		/* Set if '-' needed.		*/
	short		radix;		/* Conversion radix		*/

	while ((c = *format++) != EOS) {
	    /*
	     * Check for conversion specifications.
	     */
	    if (c == '%') {
		/*
		 * Check for various options.
		 */
		c = *format++;
		ljust = 0;			/* No left adjustment	*/
		fill = ' ';			/* Fill with spaces	*/
		if (c == '-') {			/* %-	left justify	*/
		    ljust++;
		    c = *format++;
		}
		if (c == '0') {			/* %0d	zero fill arg.	*/
		    fill = c;
		    c = *format++;
		}
		if (c == '?' || c == '*') {	/* %*	width from arg	*/
		    width = *argp++;
		    c = *format++;
		}
		else {				/* %n	field width	*/
		    width = 0;
		    while (isdigit(c)) {
			width *= 10;
			width += (c - '0');
			c = *format++;
		    }
		}
		if (c == '.') {			/* %n.n	precision	*/
		    c = *format++;		/* %n.* (from arg)	*/
		    if (c == '?' || c == '*') {
			prec = *argp++;
			c = *format++;
		    }
		    else {
			prec = 0;
			while (isdigit(c)) {
			    prec *= 10;
			    prec += (c - '0');
			    c = *format++;
			}
		    }
		}
		else {
		    prec = 32767;		/* No prec. specified	*/
		}
		/*
		 * Process conversion chars, understanding longs.
		 */
		if (c == 'l' || c == 'L') {
		    switch (c = *format++) {
		    case 'b':
		    case 'd':
		    case 'o':
		    case 'u':
		    case 'x':
			c = toupper(c);	/* Make %lb %B		*/
		    case 'B':
		    case 'D':
		    case 'O':
		    case 'U':
		    case 'X':
			break;
		    default:		/* Here on %Lfoo ==> %Ufoo	*/
			c = 'U';
			format--;
			break;
		    }
		}
		/*
		 * Search the numeric format structure for this conversion.
		 */
		for (p.fmt = formatinfo;
			(p.fmt->f_type != c && p.fmt->f_type != EOS);
			p.fmt++)
		    ;
		if (p.fmt->f_type != EOS) {
		    /*
		     * A numeric conversion was found.
		     * Get the value and expand it into the work area.
		     */
		    radix = p.fmt->f_radix;
		    temp = p.fmt->f_width;
		    p.result = &work[WORKSIZE];
		    *--p.result = EOS;		/* Terminate result	*/
		    if (temp == INT) {
			if (c == 'd' && *argp < 0) {
			    value = -*argp++;
			    temp = NEG;		/* Remember signal	*/
			}
			else
			    value = (unsigned) *argp++;
		    }
		    else {
			value = *((long *) argp)++;
		    }
#ifdef	TESTING
		    fprintf(stderr,
			"radix %d, value = %d (signed), %u (unsigned), 0x%x\n",
			radix, value, value, value);
#endif
		    if (value == 0)
			*--p.result = '0';
		    else {
			/*
			 * Convert an unsigned non-zero number.
			 */
			do {
			    c = value % radix;
			    *--p.result = c + ((c < 10) ? '0' : ('a' - 10));
			} while ((value /= radix) != 0);
			if (temp == NEG)
			    *--p.result = '-';
		    }
#ifdef	TESTING
		    fprintf(stderr, "result \"%s\"\n", p.result);
#endif
		}
		else { 
		    /*
		     * String or something
		     */		
		    switch (c) {
		    case 'q':			/* Funny int value	*/
			/*
			 * Convert a word as a pair of octal bytes.
			 */
			c = 16384;
			p.result = work;
			temp = *argp++;
			while (c > 0) {
			    *p.result++ = (temp / c) + '0';
			    temp %= c;
			    if (c == 256) {
				c = 64;
				*p.result++ = '.';
			    }
			    else
				c >>= 3;
			}
			*p.result = EOS;
			p.result = work;
			break;

		    case 'r':			/* Remote format	*/
			argp = (int *) *argp;
			format = (char *) *argp++;
			continue;		/* No print here	*/

		    case 's':			/* String		*/
			if ((p.result = (char *) *argp++) == NULL)
			    p.result = "{NULL}";	/* Bug hack	*/
			break;

		    case 'c':			/* Character		*/
			c = *argp++;
			/*
			 * Fall through
			 */
		    default:			/* %% or whatever	*/
		    	p.result = work;
			*p.result = c;
			work[1] = EOS;
			break;
		    }
		}
		/*
		 * p.result -> first byte of string to output.
		 * Reuse c as a register temp.
		 */
		c = strlen(p.result);		/* True result length	*/
		slen = (c > prec) ? prec : c;	/* Field length		*/
#ifdef	TESTING
		fprintf(stderr, "width %d, field length %d, precision %d\n",
		    width, slen, prec);
#endif
		if (!ljust) {			/* Right justify?	*/
		    while (--width >= slen) {
			putc(fill, fildes);
		    }
		}
		/*
		 * Output the string (up to "prec" bytes)
		 */
		for (c = prec; *p.result != EOS && --c >= 0;) {
		    putc(*p.result++, fildes);
		}
		if (ljust) {			/* Left justify?	*/
		    while (--width >= slen) {
			putc(' ', fildes);
		    }
		}
	    }
	    else {
		/*
		 * Not in a % thing, just output the byte.
		 */
		putc(c, fildes);
	    }
	}
	if ((fildes->_flag & _IOSTRG) != 0)
	    putc(EOS, fildes);
	return;
}

#ifdef	TESTING

char	format[133];
char	inputline[133];
int	v[3];

main() {
	extern char	*prompt();

	for (;;) {
	    if (prompt("Format") == NULL)
		break;
	    strcpy(format, inputline);
	    v[0] = rdint("v1", 1, 0, 0);
	    v[1] = rdint("v2", 1, 0, 0);
	    v[2] = rdint("v3", 1, 0, 0);
	    printf("\"");
	    c_doprnt(format, v, stdout);
	    printf("\"\n");
	}
}

char *
prompt(info)
char		*info;
/*
 * Prompt, read text from console to input line.
 * Return inputline or NULL at eof.
 */
{
	register char	*result;
	extern char	*gets();

	clearerr(stdin);
	if (info != NULL)
	    fprintf(stderr, "%s:  ", info);
	fflush(stderr);
	inputline[0] = EOS;
	result = gets(inputline);
	return (result);
}

int
rdint(info, low, high, def_value)
char		*info;			/* Prompt text			*/
int		low;			/* Minimum acceptable value	*/
int		high;			/* Maximum acceptable value	*/
int		def_value;		/* Default value		*/
/*
 * Prompt and read an integer.  Note:
 *	info		must be present
 *	low > high	any value acceptable
 */
{
	register int		value;
	register char		*lp;

	for (;;) {
	    if (low <= high) {
		fprintf(stderr, "%s <%d..%d> [%d]?  ",
		    info, low, high, def_value);
	    }
	    else {
		fprintf(stderr, "%s [%d]?  ", info, def_value);
	    }
	    if (prompt(NULL) == NULL || inputline[0] == EOS)
		sprintf(inputline, "%d", def_value);
	    value = atoi(inputline);
	    if (low > high || (value >= low && value <= high))
		break;
	    fprintf(stderr,
		"Illegal value %d, range is %d..%d, please try again.\n",
		value, low, high);
	}
	return (value);
}

#endif
