[net.sources] format

chris@sol.UUCP (07/10/86)

*** REPLACE THIS LINE WITH YOUR MESSAGE ***
Here are the routines that I talked about in net.lang.c.  They are
barely commented, and tested on XENIX on an IBM-PC/AT, ULTRIX-11 on
a PDP-11 and UNIX system V on a Microforce 1a.  Do with as ye may.
I do ask that if you make changes, please do not distribute them
as different versions will confuse people.  Notify me, and if the changes
are appropriate, I will post a new version.

Christopher M. Caldwell
...decvax!sii!dmcnh!sol!chris
16 Columbia Drive, VIBRAC Corporation, Amherst, NH 03031, (603)882-6777
13B Bobby's Lane, Milford, NH 03055, (603)673-2249

: This is a shell archive.  Delete everything above this line.
: To unpack this archive, type '/bin/sh <this_file'.
: If the message 'shar complete.' does not appear at the end, this
: file is not complete.
echo "Extracting files archived Wed Jul  9 17:53:00 1986 by chris."
echo "Extracting format.c (13553 characters)."
sed 's/^X//' << SHARMARKER > format.c
X/**************************************************************************/
X/***	format.c:	format routines					***/
X/***	Author:		Christopher M. Caldwell of IO Software, Inc.	***/
X/***	Created:	09-Jul-86					***/
X/***	"Do with as ye may"						***/
X/**************************************************************************/
X
X/*
XThis package is a set of routines for formatting data in a more flexible
Xway than UNIX's printf routines.  In the simplest case:
X
X	format("This is a test.\n");
X
Xworks the way
X
X	printf("This is a test.\n");
X
Xdoes.  However, printf uses percents (%) to denote substitutions.  Format
Xencloses substitutions with braces ({ and }).  I.E. {s} denotes "substitute
Xthe next argument as a string."  After the letter denoting the substitution
Xtype, modifiers may be supplied.  {sl5} means print a string left justified
Xto 5 characters.  If the value is not supplied, it is taken from the
Xargument list.  The EBNF for this is:
X
X	'{' <type-letter> { <modifier-character> [ <modifier-value> ] } '}'
X
XAvailable type letters so far are:
X
X	s	string (Char *)
X	i	int
X	l	long
X	c	character
X	f	float (double)
X	S	Another format string (I smell recursion)
X
XAvailable modifers (and their defaults) are:
X
X	l(0)	Number of columns to take up, value will be left justified
X	r(0)	Number of columns to take up, value will be right justified
X	c(0)	Number of columns to take up, value will be centered
X	m(0)	Maximum number of columns display of data
X	.(6)	Number of places to display to the right of the decimal point
X	b(10)	Base of number
X	p(32)	Ascii value of pad character for l, r or c.
X	u(0)	Unsigned status (signed=0, unsigned!=0)
X	n(1)	Count of times to repeat string
X
XIf a modifer value is followed by a string of digits then that string is
Xconverted to an integer and used as the modifier's value, else the value will
Xbe taken from the next argument to format.
X
XSome ridiculous variable type/modifer combinations exist:
X	What is an unsigned string (or character) of base 8 with 4 trailing
X	    decimal places? (All three modifiers are ignored)
X	Do you really want to see floating point variables in base 3?  (Yes!)
X	What does centering a left or right justifed variable look like?
X	    (The last justification character is the one used)
X
XNow, an example program:
X
X	main()
X	{
X	int i;
X
X	format("Left justified integer:  '{il10}'\n",1234);
X	format("###{f.1c}###\n",123.4,20);
X	format("Octal:  {lb8r11pu1}\n",01234L,'0');
X	format("Truncate this:  {sm10}\n", "This is a test" );
X	format("Print 5 arfs:  {sn5}\n","arf");
X	i = 1;
X	format("Do it {i} time{sn}.\n",i,"s",i!=1);
X	i = 2;
X	format("Do it {i} time{sn}.\n",i,"s",i!=1);
X	format("Center this: ###{Sc20p}###\n","Answer={i}",'$',1234);
X	exit(0);
X	}
X
Xand its standard output:
X
X	Left justified integer:  '1234      '
X	###        123.4       ###
X	Octal:  00000001234
X	Truncate this:  This is a 
X	Print 5 arfs:  arfarfarfarfarf
X	Do it 1 time.
X	Do it 2 times.
X	Center this: ###$$$$$Answer=1234$$$$###
X
XThe following flavors of format are available:
X
X	format( formatstring, args... )		Send to standard out
X	fformat( stream, formatstring, args... )Send to specified stream
X	i = cformat( formatstring, args... )	Calculate number of chars
X	sformat( buf, formatstring, args... )	Send to the character array buf
X	ptr = mformat( formatstring, args... )	Malloc enough space for result
X						(and trailing 0), put the
X						result in malloced space, and
X						return pointer to it
X			    Also, as a kluge:
X	ptr = sformat( NULL, formatstring, args... )
X
Xmformat (and sformat with a NULL array), perform the format twice,
Xonce to find out how many characters needed to malloc, and the next
Xtime to fill up the array.
X
XAdvantages:
X	Easier to understand justification
X	Centering available
X	More flexible padding (instead of just spaces or zeros)
X	Arbitrary base output (2 to 36)
X	Complex formats available ("S")
X	Arbitrarily lengthed everything (up do what you can malloc)
X	And from my point of view, I have the source!
X*/
X
X/**************************************************************************/
X
X#include <stdio.h>
X#include <ctype.h>
X
X#define PF_COUNT	0
X#define PF_ARRAY	1
X#define PF_PUTC		2
X#define then
X#define TRUE		1
X#define FALSE		0
X
Xstatic char *al;
Xextern char *malloc();
X
Xint lcase(c)	char c;		{ return( isupper(c) ? tolower(c) : c ); }
Xint ucase(c)	char c;		{ return( islower(c) ? toupper(c) : c ); }
Xint digtobin(c)	char c;		{ return( c<='9' ? c-'0' : lcase(c)-'a'+10 ); }
Xchar bintodig(i)int i;		{ return( i<=9 ? i+'0' : i+'a'-10 ); }
X
Xint isbase( c, b )
X/*	Return TRUE if character c is digit of base b.  Similar to isdigit,
X	but allows bases with letters and won't except "9" in base 8, etc.
X*/
X    char c;
X    int b;
X    {
X    if( !isdigit(c) && !isalpha(c) )
X      then return FALSE;
X      else return( c<='9' ? (c-'0'<b) : (lcase(c)-'a'<b-10) );
X    }
X
Xint cformat(fmt,arglist)		char *fmt; int arglist;
X/*	Return number of characters generated by "printing" fmt.  Used by
X	things that need to malloc space for strings.
X*/
X    {
X    int pf_count;
X    al = (char *)&arglist;
X    pf( fmt, PF_COUNT, &pf_count, NULL, NULL );
X    return pf_count;
X    }
X
Xint cxformat(fmt,arglist)		char *fmt, *arglist;
X/*	Same as above but argument list is pointed to by second argument */
X    {
X    int pf_count;
X    al = arglist;
X    pf( fmt, PF_COUNT, &pf_count, NULL, NULL );
X    return pf_count;
X    }
X
Xchar *mformat(fmt,arglist)		char *fmt; int arglist;
X/*	Return pointer to malloced array of characters	*/
X    {
X    int pf_count;
X    char *res;
X    al = (char *)&arglist;
X    pf( fmt, PF_COUNT, &pf_count, NULL, NULL );
X    if( (res=malloc(pf_count+1)) == NULL ) then return NULL;
X    al = (char *)&arglist;
X    pf( fmt, PF_ARRAY, NULL, res, NULL );
X    return res;
X    }
X
Xchar *mxformat(fmt,arglist)		char *fmt, *arglist;
X/*	Same as above but argument list is pointed to by second argument */
X    {
X    int pf_count;
X    char *res;
X    al = arglist;
X    pf( fmt, PF_COUNT, &pf_count, NULL, NULL );
X    if( (res=malloc(pf_count+1)) == NULL ) then return NULL;
X    al = arglist;
X    pf( fmt, PF_ARRAY, NULL, res, NULL );
X    return res;
X    }
X
Xchar *sformat(res,fmt,arglist)		char *fmt, *res; int arglist;
X/*	Return pointer to malloced array of characters if res==NULL,
X	else fill array res with characters.
X*/
X    {
X    int pf_count;
X    if( res==NULL )
X      then
X	{
X	al = (char *)&arglist;
X	pf( fmt, PF_COUNT, &pf_count, NULL, NULL );
X	if( (res=malloc(pf_count+1)) == NULL ) then return NULL;
X	}
X    al = (char *)&arglist;
X    pf( fmt, PF_ARRAY, NULL, res, NULL );
X    return res;
X    }
X
Xchar *sxformat(res,fmt,arglist)		char *fmt, *res, *arglist;
X/*	Same as above but argument list is pointed to by second argument */
X    {
X    int pf_count;
X    if( res==NULL )
X      then
X	{
X	al = arglist;
X	pf( fmt, PF_COUNT, &pf_count, NULL, NULL );
X	if( (res=malloc(pf_count+1)) == NULL ) then return NULL;
X	}
X    al = arglist;
X    pf( fmt, PF_ARRAY, NULL, res, NULL );
X    return res;
X    }
X
Xint format(fmt,arglist)			char *fmt; int arglist;
X/*	Print characters to standard out. */
X    {
X    al = (char *)&arglist;
X    return pf( fmt, PF_PUTC, NULL, NULL, stdout );
X    }
X
Xint xformat(fmt,arglist)		char *fmt, *arglist;
X/*	Same as above but argument list is pointed to by second argument */
X    {
X    al = arglist;
X    return pf( fmt, PF_PUTC, NULL, NULL, stdout );
X    }
X
Xint fformat(outfile,fmt,arglist)	FILE *outfile; char *fmt; int arglist;
X/*	Print characters to specified file stream. */
X    {
X    al = (char *)&arglist;
X    return pf( fmt, PF_PUTC, NULL, NULL, outfile );
X    }
X
Xint fxformat(outfile,fmt,arglist)	FILE *outfile; char *fmt, *arglist;
X/*	Same as above but argument list is pointed to by second argument */
X    {
X    al = arglist;
X    return pf( fmt, PF_PUTC, NULL, NULL, outfile );
X    }
X
X#define NEXT(mode)	((mode *)(al += sizeof(mode)))[-1]
X
Xstatic int pf( fs, pf_func, pf_count, pf_addr, pf_file )
X/*	This routine is called to parse the format string.  If pf_func
X	is PF_COUNT, that would be generated is returned.  If pf_func
X	is PF_ARRAY, an array is filled.  If pf_func is PF_FILE, characters
X	are sent to the stream pf_file.
X*/
X    char *fs;
X    int pf_func;
X    int *pf_count;
X    char *pf_addr;
X    FILE *pf_file;
X    {
X    int pf_right, pf_left, pf_center, pf_dec, pf_max;
X    int pf_base, pf_iter, pf_pad, pf_unsigned;
X    char *pf_string, pf_char;
X    int pf_int;
X    long pf_long;
X    double pf_double;
X    double pnum;
X    int ind;
X    char t, c, buf[512], *cp;
X    int bufcnt;
X    int pbase;
X    int nm;
X    char *saveal;
X
X    while( c = *fs++ )
X	if( c != '{' || (t = *fs++) == '{' )
X	  then
X	    switch( pf_func )
X		{
X		case PF_COUNT:	(*pf_count)++;		break;
X		case PF_ARRAY:	*pf_addr++ = c;		break;
X		case PF_PUTC:	if( putc(c,pf_file) == EOF )
X				  then return EOF;
X				  else break;
X		}
X	  else
X	    {
X	    pf_right	= 0;
X	    pf_left	= 0;
X	    pf_center	= 0;
X	    pf_dec	= 6;
X	    pf_max	= 0;
X	    pf_base	= 10;
X	    pf_iter	= 1;
X	    pf_pad	= ' ';
X	    pf_unsigned	= FALSE;
X	    switch( t )
X		{
X		case 'S':	pf_string	= NEXT(char *);		break;
X		case 's':	pf_string	= NEXT(char *);		break;
X		case 'c':	pf_char		= NEXT(int);		break;
X		case 'i':	pf_int		= NEXT(int);		break;
X		case 'l':	pf_long		= NEXT(long);		break;
X		case 'f':	pf_double	= NEXT(double);		break;
X		break;
X		}
X	    while( (c = *fs++) != '}' )
X		{
X		nm = 0;
X		pbase = 10;
X		if( !isbase(*fs,pbase) )
X		  then nm = NEXT(int);
X		  else
X		    {
X		    while( TRUE )
X			{
X			while( isbase(*fs,pbase) )
X			    nm = pbase*nm + digtobin(*fs++);
X			if( nm<2 || nm>36 || *fs!='_' ) then break;
X			pbase = nm;
X			fs++;
X			}
X		    }
X		switch( c )
X		    {
X		    case 'r':	pf_right = nm;		break;
X		    case 'l':	pf_left = nm;		break;
X		    case 'c':	pf_center = nm;		break;
X		    case '.':	pf_dec = nm;		break;
X		    case 'm':	pf_max = nm;		break;
X		    case 'b':	pf_base = nm;		break;
X		    case 'n':	pf_iter = nm;		break;
X		    case 'p':	pf_pad = nm;		break;
X		    case 'u':	pf_unsigned = nm;	break;
X		    }
X		}
X	    bufcnt = 0;
X	    switch( t )
X		{
X		case 's':	if( pf_string == NULL )
X				  then cp = "(null)";
X				  else cp = pf_string;
X				bufcnt = strlen( cp );
X				break;
X
X		case 'S':	saveal = al;
X				pf(pf_string,PF_COUNT,&bufcnt,NULL,NULL);
X				al = saveal;
X				cp = malloc( bufcnt+1 );
X				pf(pf_string,PF_ARRAY,NULL,cp,NULL);
X				break;
X
X		case 'c':	buf[bufcnt++] = pf_char;
X				cp = buf;
X				break;
X
X		case 'i':	pf_char = ( pf_int < 0 );
X				if( pf_int >= 0 || !pf_unsigned )
X				  then
X				    {
X				    do  {
X					buf[100-(++bufcnt)]
X					    = bintodig( abs(pf_int%pf_base) );
X					pf_int /= pf_base;
X					} while( pf_int != 0 );
X				    if(pf_char) then buf[100-(++bufcnt)]='-';
X				    cp = buf;
X				    cp += (100 - bufcnt);
X				    }
X				  else
X				    {
X				    c = pf_int & 1;
X				    pf_int >>= 1;
X				    pf_int &= (1<<(sizeof(pf_int)-1));
X				    c = c + ((pf_int%(pf_base>>1)) << 1);
X				    pf_int /= (pf_base>>1);
X				    buf[100-(++bufcnt)] = bintodig( c );
X				    while( pf_int != 0 )
X					{
X					buf[100-(++bufcnt)]
X					    = bintodig( abs(pf_int%pf_base) );
X					pf_int /= pf_base;
X					}
X				    cp = buf;
X				    cp += (100 - bufcnt);
X				    }
X				break;
X
X		case 'l':	pf_char = ( pf_long < 0 );
X				if( pf_long >= 0 || !pf_unsigned )
X				  then
X				    {
X				    do  {
X					buf[100-(++bufcnt)] = bintodig(
X					    abs((int)(pf_long%pf_base)) );
X					pf_long /= pf_base;
X					} while( pf_long != 0 );
X				    if(pf_char) then buf[100-(++bufcnt)]='-';
X				    cp = buf;
X				    cp += (100 - bufcnt);
X				    }
X				  else
X				    {
X				    c = pf_long & 1;
X				    pf_long >>= 1;
X				    pf_long &= (1<<(sizeof(pf_long)-1));
X				    c = c + ((pf_long%(pf_base>>1)) << 1);
X				    pf_long /= (pf_base>>1);
X				    buf[100-(++bufcnt)] = bintodig( c );
X				    while( pf_long != 0 )
X					{
X					buf[100-(++bufcnt)] = bintodig(
X					    abs((int)(pf_long%pf_base)) );
X					pf_long /= pf_base;
X					}
X				    cp = buf;
X				    cp += (100 - bufcnt);
X				    }
X				break;
X
X		case 'f':	if( pf_double < 0 )
X				  then
X				    {
X				    buf[bufcnt++] = '-';
X				    pf_double = -pf_double;
X				    }
X				ind = 0;
X				for(pnum=1.0; pnum<=pf_double; pnum*=pf_base)
X				    ind--;
X				pnum /= pf_base;
X				do  {
X				    if( ind++ == 0 ) then buf[bufcnt++]='.';
X				    c = (int)(pf_double/pnum);
X				    buf[bufcnt++] = bintodig( c );
X				    pf_double -= (pnum*c);
X				    pnum /= pf_base;
X				    } while( ind < pf_dec );
X				cp = buf;
X				break;
X		}
X	    if( bufcnt > pf_max && pf_max > 0 ) then bufcnt = pf_max;
X	    if( pf_center > 0 )
X	      then
X		{
X		pf_left = ( pf_center - bufcnt ) / 2;
X		pf_right = pf_center - pf_left - bufcnt;
X		}
X	      else
X		{
X		pf_left -= bufcnt;
X		pf_right -= bufcnt;
X		}
X	    while( pf_iter-- > 0 )
X		{
X		for( ind=0; ind<pf_right; ind++ )
X		    switch( pf_func )
X			{
X			case PF_COUNT:	(*pf_count)++;		break;
X			case PF_ARRAY:	*pf_addr++ = pf_pad;	break;
X			case PF_PUTC:	if( putc(pf_pad,pf_file) == EOF )
X					  then return EOF;
X					  else break;
X			}
X		for( ind=0; ind<bufcnt; ind++ )
X		    switch( pf_func )
X			{
X			case PF_COUNT:	(*pf_count)++;		break;
X			case PF_ARRAY:	*pf_addr++ = cp[ind];	break;
X			case PF_PUTC:	if( putc(cp[ind],pf_file) == EOF )
X					  then return EOF;
X					  else break;
X			}
X		for( ind=0; ind<pf_left; ind++ )
X		    switch( pf_func )
X			{
X			case PF_COUNT:	(*pf_count)++;		break;
X			case PF_ARRAY:	*pf_addr++ = pf_pad;	break;
X			case PF_PUTC:	if( putc(pf_pad,pf_file) == EOF )
X					  then return EOF;
X					  else break;
X			}
X		}
X	    if( t == 'S' ) then free( cp );
X	    }
X    if( pf_func == PF_ARRAY ) then *pf_addr = 0;
X    return 0;
X    }
SHARMARKER
echo "Shar complete."