Source code for enhanced printf.
D. Richard Hipp
drh at duke.cs.duke.edu
Fri Nov 30 08:16:04 AEST 1990
Source code for "printf()" and its cousins is attached. There are some
enhancements. Mostly, there are some new variations on "printf()" such
as:
mprintf(), snprintf(), nprintf(), and xprintf().
"mprintf()" goes to malloc for space to put its output, and returns a
pointer to that space. (Returns NULL if malloc fails, of course.) This
I have found to be the most useful. "snprintf()" is to "sprintf()" as
"strncpy()" is to "strcpy()". (Not quite. The string returned by snprintf
is always zero-terminated -- not so for strncpy.) "xprintf()" calls a
function to dispose of its output. "nprintf()" has no output -- it just
returns the number of characters that would have been output by "printf".
All the varargs variations on these are included, of course.
The second big enhancement is that new conversion format letters can
be added at run-time. Now you can easily add in customized conversion
letters for currency, dates, complex numbers, boolean values, Roman
numerals, or whatever you want.
Oh yea, this version of printf is also FASTER than the library code in
Sun OS 4.1 (Sparc).
There are also several other minor improvements. (Well, I call them
improvements. You might have other opinions...)
Send comments, bug reports, suggestions, and so forth to drh at cs.duke.edu.
-------------------------------- cut here -------------------------------
#!/bin/sh
echo extracting archive.sh
cat <<\!end-of-archive.sh! >archive.sh
#!/bin/sh
echo '#!/bin/sh' >xprintf.sh;
cat <<\! | ( while read filename; do\
echo 'archiving' $filename '...' 1>&2; \
echo 'echo extracting '$filename ;\
echo 'cat <<\!end-of-'$filename'! >'$filename ;\
cat $filename ;\
echo '!end-of-'$filename'!' ;\
done; ) >>xprintf.sh;
archive.sh
xprintf.c
xprintf.h
!
!end-of-archive.sh!
echo extracting xprintf.c
cat <<\!end-of-xprintf.c! >xprintf.c
/*
** NAME: $Source: /home/grad1/drh/res/srclib/xprintf.sh,v $
** VERSION: $Revision: 1.1 $
** DATE: $Date: 90/11/29 13:12:44 $
**
** ONELINER: A replacement for formatted printing programs.
**
** COPYRIGHT:
** Copyright (c) 1990 by D. R. Hipp. This code is an original work
** and has been prepared without reference to any prior
** implementations of similar functions. No part of this code is
** subject to licensing restrictions of any telephone company or
** university.
**
** Permission is hereby granted for this code to be used by anyone
** for any purpose under the following restrictions:
** 1. No attempt shall be made to prevent others (especially the
** author) from using this code.
** 2. Changes to this code are to be clearly marked.
** 3. The origins of this code are not to be misrepresented.
** 4. The user agrees that the author is in no way responsible for
** the correctness or usefulness of this program.
**
** DESCRIPTION:
** This program is an enhanced replacement for the "printf" programs
** found in the standard library. The following enhancements are
** supported:
**
** + Additional functions. The standard set of "printf" functions
** includes printf, fprintf, sprintf, vprintf, vfprintf, and
** vsprintf. This module adds the following:
**
** * snprintf -- Works like sprintf, but has an extra argument
** which is the size of the buffer written to.
**
** * mprintf -- Similar to sprintf. Writes output to memory
** obtained from malloc.
**
** * xprintf -- Calls a function to dispose of output.
**
** * nprintf -- No output, but returns the number of characters
** that would have been output by printf.
**
** * A v- version (ex: vsnprintf) of every function is also
** supplied.
**
** + A few extensions to the formatting notation are supported:
**
** * The %b field outputs an integer in binary notation.
**
** * The %c field now accepts a precision. The character output
** is repeated by the number of times the precision specifies.
**
** * The %' field works like %c, but takes as its character the
** next character of the format string, instead of the next
** argument. For example, printf("%.78'-") prints 78 minus
** signs, the same as printf("%.78c",'-').
**
** + The user can add a limited number (20) of new conversion fields
** at run-time using the "converter" function. Test case 5 below
** shows an example of how this is done.
**
** + When compiled using GCC on a SPARC, this version of printf is
** faster than the library printf for SUN OS 4.1.
**
** + All functions (except converter) are fully reentrant.
**
** TESTING
** Use the following command sequence to run 5 tests on this code:
**
** gcc -DTEST1 xprintf.c Test integer conversion
** a.out
** gcc -DTEST2 xprintf.c Test string and character output
** a.out
** gcc -DTEST3 xprintf.c Test floating point conversions
** a.out
** gcc -DTEST4 xprintf.c Test the library
** a.out
** gcc -DTEST5 xprintf.c Test the "converter" function
** a.out
**
*/
/*
** Undefine COMPATIBILITY to make some slight changes in the way things
** work. I think the changes are an improvement, but they are not
** backwards compatible.
*/
#define COMPATIBILITY /* Compatible with SUN OS 4.1 */
#ifndef NOINTERFACE
# if defined(TEST1) || defined(TEST2) || defined(TEST3)
# define NOINTERFACE
# else
# define printf DUMMY_FUNCTION_1 /* I don't want prototypes */
# define fprintf DUMMY_FUNCTION_2 /* for any of the standard */
# define sprintf DUMMY_FUNCTION_3 /* functions, because such */
# define vprintf DUMMY_FUNCTION_4 /* prototypes could clash with */
# define vfprintf DUMMY_FUNCTION_5 /* my redefinitions */
# define vsprintf DUMMY_FUNCTION_6
# endif
#endif
#include <ctype.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include "xprintf.h"
#ifndef NOINTERFACE
# undef printf
# undef fprintf
# undef sprintf
# undef vprintf
# undef vfprintf
# undef vsprintf
#endif
/*
** Conversion types fall into various categories as defined by the
** following enumeration.
*/
enum e_type { /* The type of the format field */
RADIX, /* Integer types. %d, %x, %o, and so forth */
FLOAT, /* Floating point. %f */
EXP, /* Exponentional notation. %e and %E */
GENERIC, /* Floating or exponential, depending on exponent. %g */
SIZE, /* Return number of characters processed so far. %n */
STRING, /* Strings. %s */
PERCENT, /* Percent symbol. %% */
CHAR, /* Characters. %c */
CHARLIT, /* Literal characters. %' */
USER, /* User specified conversion */
ERROR, /* Used to indicate no such conversion type */
};
/*
** Each builtin conversion character (ex: the 'd' in "%d") is described
** by an instance of the following structure
*/
typedef struct s_info { /* Information about each format field */
int fmttype; /* The format field code letter */
int base; /* The base for radix conversion */
char *charset; /* The character set for conversion */
int flag_signed; /* Is the quantity signed? */
char *prefix; /* Prefix on non-zero values in alt format */
enum e_type type; /* Conversion paradigm */
} info;
/*
** The following table is searched linearly, so it is good to put the
** most frequently used conversion types first.
*/
static const info fmtinfo[] = {
'd', 10, "0123456789", 1, 0, RADIX,
's', 0, 0, 0, 0, STRING,
'c', 0, 0, 0, 0, CHAR,
'o', 8, "01234567", 0, "0", RADIX,
'u', 10, "0123456789", 0, 0, RADIX,
'x', 16, "0123456789abcdef", 0, "x0", RADIX,
'X', 16, "0123456789ABCDEF", 0, "X0", RADIX,
'f', 0, 0, 1, 0, FLOAT,
'e', 0, "e", 1, 0, EXP,
'E', 0, "E", 1, 0, EXP,
'g', 0, "e", 1, 0, GENERIC,
'G', 0, "E", 1, 0, GENERIC,
'i', 10, "0123456789", 1, 0, RADIX,
'n', 0, 0, 0, 0, SIZE,
'%', 0, 0, 0, 0, PERCENT,
'b', 2, "01", 0, "b0", RADIX, /* Binary notation */
'p', 10, "0123456789", 0, 0, RADIX, /* Pointers */
'\'', 0, 0, 0, 0, CHARLIT, /* Literal character */
};
#define NINFO (sizeof(fmtinfo)/sizeof(info)) /* Size of the fmtinfo table */
/*
** If NOFLOATINGPOINT is defined, then none of the floating point
** conversions will work.
*/
#ifndef NOFLOATINGPOINT
/*
** "*val" is a double such that 0.1 <= *val < 10.0
** Return the ascii code for the leading digit of *val, then
** multiply "*val" by 10.0 to renormalize.
**
** Example:
** input: *val = 3.14159
** output: *val = 1.4159 function return = '3'
*/
static int getdigit(double *val){
int digit;
double d;
digit = (int)*val;
d = digit;
digit += '0';
*val = (*val - d)*10.0;
return digit;
}
#endif
/*
** User specified conversion fields.
*/
/*
** For internal use only: This structure remembers the details of a
** user defined conversion.
*/
typedef struct s_userinfo {
char format; /* The conversion format character */
int (*func)(convertctrl*); /* The function to call to do the convert */
void *arg; /* A generic argument to (*func)(...) */
} userinfo;
/*
** Here is an array of the user defined conversions. There are a fixed
** number.
*/
#define NUSERFMT 20 /* Max no. of user-defined conversions */
static int nuserfmt = 0; /* No. of previously defined */
static userinfo userfmt[NUSERFMT]; /* Array of user-defined conversions */
/*
** Call this function in order to install a new user-specified conversion
** field.
**
** The function returns TRUE if the conversion is successfully installed.
*/
int converter(char format, int (*func)(convertctrl*), void *arg){
int rc;
if( nuserfmt<NUSERFMT ){
userfmt[nuserfmt].format = format;
userfmt[nuserfmt].func = func;
userfmt[nuserfmt].arg = arg;
nuserfmt++;
rc = 1;
}else{
rc = 0;
}
return rc;
}
#ifndef TEST4
# define BUFSIZE 1000 /* Size of the output buffer */
/* NOTE: No field can be longer than BUFSIZE characters! */
#else
# define BUFSIZE 7 /* Set low to exercise code during TEST4 */
#endif
/*
** The root program. All variations call this core.
**
** INPUTS:
** func This is a pointer to a function taking three arguments
** 1. A pointer to the list of characters to be output
** (Note, this list is NOT null terminated.)
** 2. An integer number of characters to be output.
** (Note: This number might be zero.)
** 3. A pointer to anything. Same as the "arg" parameter.
**
** arg This is the pointer to anything which will be passed as the
** third argument to "func". Use it for whatever you like.
**
** fmt This is the format string, as in the usual printf.
**
** ap This is a pointer to a list of arguments. Same as in
** vfprintf.
**
** OUTPUTS:
** The return value is the total number of characters sent to
** the function "func". Returns EOF on a error.
**
** Note that the order in which automatic variables are declared below
** seems to make a big difference in determining how fast this beast
** will run.
*/
int vxprintf(void (*func)(char*,int,void*),
void *arg, const char *format, va_list ap){
register const char *fmt; /* The format string. */
register int c; /* Next character in the format string */
register char *bufpt; /* Pointer to the conversion buffer */
register int precision; /* Precision of the current field */
register int length; /* Length of the field */
register int idx; /* A general purpose loop counter */
int count; /* Total number of characters output */
int width; /* Width of the current field */
int flag_leftjustify; /* True if "-" flag is present */
int flag_plussign; /* True if "+" flag is present */
int flag_blanksign; /* True if " " flag is present */
int flag_alternateform; /* True if "#" flag is present */
int flag_zeropad; /* True if field width constant starts with zero */
int flag_long; /* True if "l" flag is present */
unsigned long longvalue; /* Value for integer types */
double realvalue; /* Value for real types */
const info *infop; /* Pointer to the appropriate info structure */
char buf[BUFSIZE]; /* Conversion buffer */
char prefix; /* Prefix character. "+" or "-" or " " or 0. */
int errorflag = 0; /* True if an error is encountered */
enum e_type xtype; /* Which of 3 different FP formats */
convertctrl v; /* Used for calling user conversion routines */
userinfo *ufp = 0; /* Pointer to info about a user conversion */
static char spaces[]=" ";
#define SPACESIZE (sizeof(spaces)-1)
#ifndef NOFLOATINGPOINT
int exp; /* exponent of real numbers */
double rounder; /* Used for rounding floating point values */
int flag_dp; /* True if decimal point should be shown */
int flag_rtz; /* True if trailing zeros should be removed */
#endif
fmt = format; /* Put in a register for speed */
count = length = 0;
bufpt = 0;
for(; (c=(*fmt))!=0; ++fmt){
if( c!='%' ){
register int amt;
bufpt = (char *)fmt;
amt = 1;
while( (c=*++fmt)!='%' && c!=0 ) amt++;
(*func)(bufpt,amt,arg);
count += amt;
if( c==0 ) break;
}
if( (c=(*++fmt))==0 ){
errorflag = 1;
(*func)("%",1,arg);
count++;
break;
}
/* Find out what flags are present */
flag_leftjustify = flag_plussign = flag_blanksign =
flag_alternateform = flag_zeropad = 0;
do{
switch( c ){
case '-': flag_leftjustify = 1; c = 0; break;
case '+': flag_plussign = 1; c = 0; break;
case ' ': flag_blanksign = 1; c = 0; break;
case '#': flag_alternateform = 1; c = 0; break;
case '0': flag_zeropad = 1; c = 0; break;
default: break;
}
}while( c==0 && (c=*++fmt)!=0 );
/* Get the field width */
width = 0;
if( c=='*' ){
width = va_arg(ap,int);
if( width<0 ){
flag_leftjustify = 1;
width = -width;
}
c = *++fmt;
}else{
while( isdigit(c) ){
width = width*10 + c - '0';
c = *++fmt;
}
}
/* Get the precision */
if( c=='.' ){
precision = 0;
c = *++fmt;
if( c=='*' ){
precision = va_arg(ap,int);
#ifndef COMPATIBILITY
/* This is sensible, but SUN OS 4.1 doesn't do it. */
if( precision<0 ) precision = -precision;
#endif
c = *++fmt;
}else{
while( isdigit(c) ){
precision = precision*10 + c - '0';
c = *++fmt;
}
}
/* Limit the precision to prevent overflowing buf[] during conversion */
if( precision>BUFSIZE-40 ) precision = BUFSIZE-40;
}else{
precision = -1;
}
/* Get the conversion type modifier */
if( c=='l' ){
flag_long = 1;
c = *++fmt;
}else{
flag_long = 0;
}
/* Fetch the info entry for the field */
infop = 0;
for(idx=0; idx<NINFO; idx++){
if( c==fmtinfo[idx].fmttype ){
infop = &fmtinfo[idx];
break;
}
}
/* No info entry found. Perhaps this is a user-specified conversion.
** Otherwise, it must be an error. Check it out. */
if( infop==0 ){
int i;
xtype = ERROR;
for(i=0; i<nuserfmt; i++){
if( userfmt[i].format==c ){
ufp = &userfmt[i];
xtype = USER;
break;
}
}
}else{
xtype = infop->type;
}
/*
** At this point, variables are initialized as follows:
**
** flag_alternateform TRUE if a '#' is present.
** flag_plussign TRUE if a '+' is present.
** flag_leftjustify TRUE if a '-' is present or if the
** field width was negative.
** flag_zeropad TRUE if the width began with 0.
** flag_long TRUE if the letter 'l' (ell) prefixed
** the conversion character.
** flag_blanksign TRUE if a ' ' is present.
** width The specified field width. This is
** always non-negative. Zero is the default.
** precision The specified precision. The default
** is -1.
** xtype The class of the conversion.
** infop Pointer to the appropriate info struct.
** ufp Pointer to an approariate userinfo struct.
*/
switch( xtype ){
case RADIX:
if( flag_long ) longvalue = va_arg(ap,long);
else longvalue = va_arg(ap,int);
#ifdef COMPATIBILITY
/* For the format %#x, the value zero is printed "0" not "0x0".
** I think this is stupid. */
if( longvalue==0 ) flag_alternateform = 0;
#else
/* More sensible: turn off the prefix for octal (to prevent "00"),
** but leave the prefix for hex. */
if( longvalue==0 && infop->base==8 ) flag_alternateform = 0;
#endif
if( infop->flag_signed ){
if( *(long*)&longvalue<0 ){
longvalue = -*(long*)&longvalue;
prefix = '-';
}else if( flag_plussign ) prefix = '+';
else if( flag_blanksign ) prefix = ' ';
else prefix = 0;
}else prefix = 0;
if( flag_zeropad && precision<width-(prefix!=0) ){
precision = width-(prefix!=0);
}
{
register char *cset; /* Use registers for speed */
register int base;
cset = infop->charset;
base = infop->base;
bufpt = &buf[BUFSIZE];
do{ /* Convert to ascii */
*(--bufpt) = cset[longvalue%base];
longvalue = longvalue/base;
}while( longvalue>0 );
}
length = (int)&buf[BUFSIZE]-(int)bufpt;
for(idx=precision-length; idx>0; idx--){
*(--bufpt) = '0'; /* Zero pad */
}
if( prefix ) *(--bufpt) = prefix; /* Add sign */
if( flag_alternateform && infop->prefix ){ /* Add "0" or "0x" */
char *pre, x;
pre = infop->prefix;
if( *bufpt!=pre[0] ){
for(pre=infop->prefix; (x=*pre)!=0; pre++) *(--bufpt) = x;
}
}
length = (int)&buf[BUFSIZE]-(int)bufpt;
break;
case FLOAT:
case EXP:
case GENERIC:
realvalue = va_arg(ap,double);
#ifndef NOFLOATINGPOINT
if( precision<0 ) precision = 6; /* Set default precision */
if( precision>BUFSIZE-10 ) precision = BUFSIZE-10;
if( realvalue<0.0 ){
realvalue = -realvalue;
prefix = '-';
}else{
if( flag_plussign ) prefix = '+';
else if( flag_blanksign ) prefix = ' ';
else prefix = 0;
}
if( infop->type==GENERIC && precision>0 ) precision--;
for(idx=precision, rounder=0.5; idx>0; idx--, rounder*=0.1);
if( infop->type==FLOAT ) realvalue += rounder;
/* Normalize realvalue to within 10.0 > realvalue >= 1.0 */
exp = 0;
while( realvalue>=1e8 ){ realvalue *= 1e-8; exp+=8; }
while( realvalue>=10.0 ){ realvalue *= 0.1; exp++; }
while( realvalue<1e-8 ){ realvalue *= 1e8; exp-=8; }
while( realvalue<1.0 ){ realvalue *= 10.0; exp--; }
bufpt = buf;
/*
** If the field type is GENERIC, then convert to either EXP
** or FLOAT, as appropriate.
*/
if( xtype==GENERIC ){
flag_rtz = !flag_alternateform;
if( exp<-4 || exp>precision ){
xtype = EXP;
}else{
precision = precision - exp;
realvalue += rounder;
xtype = FLOAT;
}
}
/*
** The "exp+precision" test causes output to be of type EXP if
** the precision is too large to fit in buf[].
*/
if( xtype==FLOAT && exp+precision<BUFSIZE-30 ){
flag_rtz = 0;
flag_dp = (precision>0 || flag_alternateform);
if( prefix ) *(bufpt++) = prefix; /* Sign */
if( exp<0 ) *(bufpt++) = '0'; /* Digits before "." */
else for(; exp>=0; exp--) *(bufpt++) = getdigit(&realvalue);
if( flag_dp ) *(bufpt++) = '.'; /* The decimal point */
for(exp++; exp<0 && precision>0; precision--, exp++){
*(bufpt++) = '0';
}
while( (precision--)>0 ) *(bufpt++) = getdigit(&realvalue);
*(bufpt--) = 0; /* Null terminate */
if( flag_rtz && flag_dp ){ /* Remove trailing zeros and "." */
while( bufpt>=buf && *bufpt=='0' ) *(bufpt--) = 0;
if( bufpt>=buf && *bufpt=='.' ) *(bufpt--) = 0;
}
bufpt++; /* point to next free slot */
}else{ /* EXP */
flag_rtz = 0;
flag_dp = (precision>0 || flag_alternateform);
realvalue += rounder;
if( prefix ) *(bufpt++) = prefix; /* Sign */
*(bufpt++) = getdigit(&realvalue); /* First digit */
if( flag_dp ) *(bufpt++) = '.'; /* Decimal point */
while( (precision--)>0 ) *(bufpt++) = getdigit(&realvalue);
bufpt--; /* point to last digit */
if( flag_rtz && flag_dp ){ /* Remove tail zeros */
while( bufpt>=buf && *bufpt=='0' ) *(bufpt--) = 0;
if( bufpt>=buf && *bufpt=='.' ) *(bufpt--) = 0;
}
bufpt++; /* point to next free slot */
*(bufpt++) = infop->charset[0];
if( exp<0 ){ *(bufpt++) = '-'; exp = -exp; } /* sign of exp */
else { *(bufpt++) = '+'; }
if( exp>=100 ) *(bufpt++) = (exp/100)+'0'; /* 100's digit */
*(bufpt++) = exp/10+'0'; /* 10's digit */
*(bufpt++) = exp%10+'0'; /* 1's digit */
}
/* The converted number is in buf[] and zero terminated. Output it.
** Note that the number is in the usual order, not reversed as with
** integer conversions. */
length = (int)bufpt-(int)buf;
bufpt = buf;
#endif
break;
case SIZE:
*(va_arg(ap,int*)) = count;
length = width = 0;
break;
case PERCENT:
buf[0] = '%';
bufpt = buf;
length = 1;
break;
case CHARLIT:
case CHAR:
c = buf[0] = (xtype==CHAR ? va_arg(ap,int) : *++fmt);
if( precision>=0 ){
for(idx=1; idx<precision; idx++) buf[idx] = c;
length = precision;
}else{
length =1;
}
bufpt = buf;
break;
case STRING:
bufpt = va_arg(ap,char*);
if( bufpt==0 ) bufpt = "(null)";
length = strlen(bufpt);
if( precision>=0 && precision<length ) length = precision;
break;
case USER:
v.bufsize = BUFSIZE;
v.buf = buf;
v.precision = precision;
v.flag_sharp = flag_alternateform;
v.flag_plus = flag_plussign;
v.flag_blank = flag_blanksign;
v.ap = ap;
v.arg = ufp->arg;
length = (*(ufp->func))(&v);
ap = v.ap;
bufpt = buf;
break;
case ERROR:
buf[0] = '%';
buf[1] = c;
errorflag = 0;
idx = 1+(c!=0);
(*func)("%",idx,arg);
count += idx;
if( c==0 ) fmt--;
break;
}/* End switch over the format type */
/*
** The text of the conversion is pointed to by "bufpt" and is
** "length" characters long. The field width is "width". Do
** the output.
*/
if( !flag_leftjustify ){
register int nspace;
nspace = width-length;
if( nspace>0 ){
count += nspace;
while( nspace>=SPACESIZE ){
(*func)(spaces,SPACESIZE,arg);
nspace -= SPACESIZE;
}
if( nspace>0 ) (*func)(spaces,nspace,arg);
}
}
if( length>0 ){
(*func)(bufpt,length,arg);
count += length;
}
if( flag_leftjustify ){
register int nspace;
nspace = width-length;
if( nspace>0 ){
count += nspace;
while( nspace>=SPACESIZE ){
(*func)(spaces,SPACESIZE,arg);
nspace -= SPACESIZE;
}
if( nspace>0 ) (*func)(spaces,nspace,arg);
}
}
}/* End for loop over the format string */
return errorflag ? EOF : count;
} /* End of function */
int xprintf(void (*func)(char*,int,void*), void *arg, const char *fmt, ...){
va_list ap;
va_start(ap,fmt);
return vxprintf(func,arg,fmt,ap);
}
#ifndef NOINTERFACE
/*
** The following set of routines implement convenient interfaces to the
** xprintf functions. These are as follows:
**
** [v][f]printf The usual library routines.
** [v]sprintf For generating strings.
** [v]snprintf Like sprintf, but the strings are limited in
** length to avoid overflowing buffers.
** [v]mprintf Gets space to hold the string from malloc
** and returns a pointer to that space.
** [v]nprintf Do no printing. Measure the size of the
** output string if it were to be printed.
**
** First, the regular print, and its variants, as found in any standard
** library.
*/
static void fout(register char *txt, register int amt, register void *arg){
register int c;
while( amt-->0 ){
c = *txt++;
putc(c,(FILE *)arg);
}
}
int printf(const char *fmt, ...){
va_list ap;
va_start(ap,fmt);
return vxprintf(fout,(void*)stdout,fmt,ap);
}
int vprintf(const char *fmt, va_list ap){
return vxprintf(fout,(void*)stdout,fmt,ap);
}
int fprintf(FILE *fp, const char *fmt, ...){
va_list ap;
va_start(ap,fmt);
return vxprintf(fout,(void*)fp,fmt,ap);
}
int vfprintf(FILE *fp, const char *fmt, va_list ap){
return vxprintf(fout,(void*)fp,fmt,ap);
}
/*
** Now for string-printf, also as found in any standard library.
** Add to this the snprintf function which stops added characters
** to the string at a given length.
**
** Note that snprintf returns the length of the string as it would
** be if there were no limit on the output.
*/
typedef struct s_strargument { /* Describes the string being written to */
char *next; /* Next free slot in the string */
char *last; /* Last available slot in the string */
} sarg;
static void sout(char *txt, int amt, void *arg){
register char *head;
register char *t;
register int a;
register char *tail;
a = amt;
t = txt;
head = ((sarg*)arg)->next;
tail = ((sarg*)arg)->last;
if( tail ){
while( a-->0 && head<tail ) *(head++) = *(t++);
}else{
while( a-->0 ) *(head++) = *(t++);
}
*head = 0;
((sarg*)arg)->next = head;
}
int sprintf(char *buf, const char *fmt, ...){
sarg arg;
va_list ap;
va_start(ap,fmt);
arg.next = buf;
arg.last = 0;
return vxprintf(sout,(void*)&arg,fmt,ap);
}
int vsprintf(char *buf, const char *fmt, va_list ap){
sarg arg;
arg.next = buf;
arg.last = 0;
return vxprintf(sout,(void*)&arg,fmt,ap);
}
int snprintf(char *buf, int n, const char *fmt, ...){
va_list ap;
sarg arg;
va_start(ap,fmt);
arg.next = buf;
arg.last = &buf[n-1];
return vxprintf(sout,(void*)&arg,fmt,ap);
}
int vsnprintf(char *buf, int n, const char *fmt, va_list ap){
sarg arg;
arg.next = buf;
arg.last = &buf[n-1];
return vxprintf(sout,(void*)&arg,fmt,ap);
}
/*
** The following are for malloc-printf. Space is obtained from malloc
** to hold the string which results, and a pointer to this space is
** returned. NULL is returned if we run out of space.
*/
typedef struct s_mprintfarg {
char *buf;
int size;
char *next;
} marg;
static void mout(char *txt, int amt, void *arg){
register char *head = 0;
register char *t;
register int a;
marg *mp;
mp = (marg*)arg;
a = amt;
t = txt;
if( mp->size==0 ){
mp->size = amt+1;
head = mp->buf = malloc( mp->size );
if( head ){
while( a-->0 ) *(head++) = *(t++);
*head = 0;
}
}else{
if( mp->buf!=0 ){
mp->buf = realloc(mp->buf,mp->size+amt);
if( mp->buf ){
head = &(mp->buf[mp->size-1]);
while( a-->0 ) *(head++) = *(t++);
*head = 0;
mp->size += amt;
}
}
}
mp->next = head;
}
char *mprintf(const char *fmt, ...){
va_list ap;
marg mp;
va_start(ap,fmt);
mp.size = 0;
mp.buf = mp.next = 0;
vxprintf(mout,(void*)&mp,fmt,ap);
return mp.buf;
}
char *vmprintf(const char *fmt, va_list ap){
marg mp;
mp.size = 0;
mp.buf = mp.next = 0;
vxprintf(mout,(void*)&mp,fmt,ap);
return mp.buf;
}
/*
** Null-printf does nothing but measure the size of the output
** string.
*/
static void nout(void){
return;
}
int nprintf(const char *fmt, ...){
va_list ap;
va_start(ap,fmt);
return vxprintf((void(*)(char*,int,void*))nout,(void*)0,fmt,ap);
}
int vnprintf(const char *fmt, va_list ap){
return vxprintf((void(*)(char*,int,void*))nout,(void*)0,fmt,ap);
}
#endif
#ifdef TEST1
/****************************************************************************
* Test integer conversions *
****************************************************************************/
#ifndef NOINTERFACE
Sorry.... This test will not work if the interface is
turned on.
#endif
char outbuf[1000];
int outidx = 0;
int fout(char *str, int amt){
strncpy(&outbuf[outidx],str,amt);
outidx += amt;
return 0;
}
int testprintf(char *fmt, ...){
va_list ap;
va_start(ap,fmt);
return vxprintf((void(*)(char*,int,void*))fout,0,fmt,ap);
}
#include <a.out.h>
void moncontrol(int);
void main(){
static char letters[] = "dixXou%";
static char *flags[] = { "", "-", " ", "+", "#", "-#", "+-", "+#", "# ",
"-+#", "# -+", 0 };
static char *widths[] = { "", "0", "05", "1", "3", "10", "*", 0 };
static char *precisions[] = { "", ".1", ".3", ".10", ".*", 0 };
int values[] = { 0, 1, 10, 9999, -1, -10, -9999 };
char format[100];
char sysout[1000];
int longflag;
int sysrc;
int vdrc;
int lcnt;
int fcnt;
int wcnt;
int pcnt;
int vcnt;
int v1, v2, v3;
int count = 0;
moncontrol(0);
lcnt = fcnt = vcnt = longflag = wcnt = pcnt = v1 = v2 = v3 = 0;
do{
sprintf(format,"Ho ho ho %%%s%s%s%s%c hi hi hi",
flags[fcnt],widths[wcnt],precisions[pcnt],
longflag?"l":"",letters[lcnt]);
v1 = values[vcnt];
if( widths[wcnt][0]=='*' ){
v2 = v1;
v1 = 8;
}
if( precisions[pcnt][1]=='*' ){
v3 = v2;
v2 = v1;
v1 = 7;
}
outidx = 0;
moncontrol(1);
if( longflag ){
sprintf(sysout,format,(long)v1,v2,v3);
vdrc = testprintf(format,(long)v1,v2,v3);
}else{
sprintf(sysout,format,v1,v2,v3);
vdrc = testprintf(format,v1,v2,v3);
}
moncontrol(0);
sysrc = strlen(sysout);
outbuf[outidx] = 0;
count++;
if( count%1000==0 ){
printf("\rChecked thru %d... ",count);
fflush(stdout);
}
if( vdrc!=sysrc || strcmp(outbuf,sysout)!=0 ){
printf("Format: [%s] Arguments: %d, %d, %d\n",format,v1,v2,v3);
printf(" system: [%s] returned %d\n",sysout,sysrc);
printf(" vxprintf: [%s] returned %d\n",outbuf,vdrc);
}
vcnt++;
if( vcnt>=sizeof(values)/sizeof(int) ){ vcnt = 0; lcnt++; }
if( letters[lcnt]==0 ){ lcnt = 0; longflag++; }
if( longflag==2 ){ longflag = 0; fcnt++; }
if( flags[fcnt]==0 ){ fcnt = 0; wcnt++; }
if( widths[wcnt]==0 ){ wcnt = 0; pcnt++; }
}while( precisions[pcnt] );
printf("\nChecked %d combinations.\n",count);
}
#endif
#ifdef TEST2
/****************************************************************************
* Test string conversions *
****************************************************************************/
#ifndef NOINTERFACE
Sorry.... This test will not work if the interface is
turned on.
#endif
char outbuf[1000];
int outidx = 0;
int fout(char *str, int amt){
strncpy(&outbuf[outidx],str,amt);
outidx += amt;
return 0;
}
int testprintf(char *fmt, ...){
va_list ap;
va_start(ap,fmt);
return vxprintf((void(*)(char*,int,void*))fout,0,fmt,ap);
}
void main(){
int count = 0;
char sysout[1000];
int sysrc;
int vdrc;
int i;
int sysx;
int vdx;
for(i=0; i<50; i++){
sprintf(sysout,"%*s%n--%c ho ho ho",i,(i&1)?"xxx":"yyyyyyyy",&sysx,i+'a');
sysrc = strlen(sysout);
outidx = 0;
vdrc = testprintf("%*s%n--%c ho ho ho",i,(i&1)?"xxx":"yyyyyyyy",&vdx,i+'a');
outbuf[outidx] = 0;
count++;
if( vdrc!=sysrc || strcmp(outbuf,sysout)!=0 || sysx!=vdx ){
printf(" system: [%s] returned %d with x=%d\n",sysout,sysrc,sysx);
printf(" vxprintf: [%s] returned %d with x=%d\n",outbuf,vdrc,vdx);
}
}
printf("Checked %d combinations.\n",count);
}
#endif
#ifdef TEST3
/****************************************************************************
* Test floating-point conversions *
****************************************************************************/
#ifndef NOINTERFACE
Sorry.... This test will not work if the interface is
turned on.
#endif
char outbuf[1000];
int outidx = 0;
int fout(char *str, int amt){
strncpy(&outbuf[outidx],str,amt);
outidx += amt;
return 0;
}
int testprintf(char *fmt, ...){
va_list ap;
va_start(ap,fmt);
return vxprintf((void(*)(char*,int,void*))fout,0,fmt,ap);
}
#include <a.out.h>
void moncontrol(int);
/*
** compare number strings. Ignore errors past the first 15 significant
** digits. Return TRUE for a match, and FALSE for a mismatch.
*/
int rcmp(char *a, char *b){
int digitcnt = 0;
while( *a && *b ){
if( (!isdigit(*a) || ++digitcnt<15) && *a!=*b ){
return 0;
}
if( !isdigit(*a) && *a!='.' ) digitcnt = 0;
a++;
b++;
}
return *a==*b;
}
void main(){
int count = 0;
char sysout[1000];
int sysrc;
int vdrc;
double value;
int w,p;
static char *format[] = { "%*.*g","%-*.*g","%#*.*g","%+*.*g", "% *.*G",
"%*.*e", "%#*.*E", "%*.*f", "%#*.*f", "%*%%*g"};
int fcnt;
moncontrol(0);
for(fcnt=0; fcnt<sizeof(format)/sizeof(char*); fcnt++){
for(w=0; w<=12; w += 3){
for(p=0; p<=12; p += 3){
for(value=1.234567890123456789e-32; value<1.3e+32; value *= 1e8 ){
outidx = 0;
moncontrol(1);
sprintf(sysout,format[fcnt],w,p,value);
vdrc = testprintf(format[fcnt],w,p,value);
moncontrol(0);
sysrc = strlen(sysout);
outbuf[outidx] = 0;
count++;
if( vdrc!=sysrc || !rcmp(outbuf,sysout) ){
printf("Count=%d Format=%s Width=%d Precision=%d Value=%.16g\n",
count,format[fcnt],w,p,value);
printf(" system: [%s] returned %d\n",sysout,sysrc);
printf(" vxprintf: [%s] returned %d\n",outbuf,vdrc);
}
}
}
}
}
printf("Checked %d combinations.\n",count);
}
#endif
#ifdef TEST4
/****************************************************************************
* Test the interface routines *
****************************************************************************/
#define BSIZE 71
void main(void){
char buf[BSIZE];
char buf2[BSIZE];
char *cp;
int i;
for(i=0; i<BSIZE; i++) buf[i] = (i%10)+'0';
buf[BSIZE-1] = 0;
printf("This is a test of all the interface routines:\n");
printf("vprintf:\n");
vprintf(buf,0);
printf("\nfprintf:\n");
fprintf(stdout,buf);
printf("\nvfprintf:\n");
vfprintf(stdout,buf,0);
printf("\nsprintf:\n");
sprintf(buf2,buf);
printf(buf2);
printf("\nvsprintf:\n");
vsprintf(buf2,buf,0);
printf(buf2);
printf("\nsnprintf: (n=52)\n");
snprintf(buf2,52,buf);
printf(buf2);
printf("\nvsnprintf: (n=17)\n");
vsnprintf(buf2,17,buf,0);
printf(buf2);
printf("\nmprintf:\n");
cp = mprintf(buf);
printf(cp);
free(cp);
printf("\nvmprintf:\n");
cp = vmprintf(buf,0);
printf(cp);
free(cp);
printf("\nnprintf:\n");
i = nprintf(buf);
printf("(returns %d)\n",i);
printf("vnprintf:\n");
i = vnprintf(buf,0);
printf("(returns %d)\n",i);
}
#endif
#ifdef TEST5
/****************************************************************************
* Test the user-specified conversion logic *
****************************************************************************/
/*
** A routine to print integer number of cents as a dollar amount.
*/
int dollar(convertctrl *vp){
int d,neg;
char *front, *back;
int size, fbsize;
int prec;
d = va_arg(vp->ap,int);
if( d<0 ){
d = -d;
neg = 1;
}else{
neg = 0;
}
prec = vp->precision;
back = front = "";
if( neg ){
if( vp->flag_sharp ){
front = "(";
back = ")";
}else{
front = "-";
}
}else{
if( vp->flag_sharp ) back = " ";
if( vp->flag_plus ) front = "+";
if( vp->flag_blank ) front = " ";
}
fbsize = (*front!=0) + (*back!=0);
size = sprintf(vp->buf,"%s%d.%.2d%s",front,d/100,d%100,back);
if( prec>0 && size>prec ){
size = sprintf(vp->buf,"%s%.*'*.**%s",front,prec-fbsize-3,back);
}
return size;
}
char *fmt[] = { "%$", "%#$", "%#10.7$", "%-10.5$", "%#.8$",
"%+.10$", "% #8.8$" };
#define nfmt (sizeof(fmt)/sizeof(char*))
int value[] = { 0, 1, 100, 123456789, -1, -100, -123456789 };
#define nvalue (sizeof(value)/sizeof(int))
void main(void){
if( !converter('$',dollar,0) ){
printf("Call to converter() failed...\n");
}else{
int i,j;
for(i=0; i<nfmt; i++){
for(j=0; j<nvalue; j++){
int x;
x = printf("format=\"%s\" value=%d",fmt[i],value[j]);
printf("%.*' result=\"",40-x);
printf(fmt[i],value[j]);
printf("\"\n");
}
}
}
}
#endif
!end-of-xprintf.c!
echo extracting xprintf.h
cat <<\!end-of-xprintf.h! >xprintf.h
/*
** Extra printf routines implemented by xprintf.c which are not found
** in standard libraries are declared here.
*/
#ifndef XPRINTFH
#define XPRINTFH
#include <stdarg.h>
int xprintf(void (*)(char*,int,void*), void*, const char*, ...);
int vxprintf(void (*)(char*,int,void*), void*, const char*, va_list);
int snprintf(char*, int, const char*, ...);
int vsnprintf(char*, int, const char*, va_list);
char *mprintf(const char*, ...);
char *vmprintf(const char*, va_list);
int nprintf(const char*, ...);
int vnprintf(const char*, va_list);
/*
** The following structure is used to pass information from vxprintf to
** the user specified conversion routine for user specified conversions.
**
** The user conversion routine should read the next argument using
** the "ap" field of this structure, convert this argument into text
** of "bufsize" or fewer characters, place this text in "*buf", then
** return the length of the text. Information about the precision and
** values of flags are supplied. (If no precision is specified, then
** a value of -1 is put in the "precision" field.)
*/
typedef struct s_cvertctrl {
int bufsize; /* Size of buf[] */
char *buf; /* Start of buffer into which conversion is written */
int precision; /* Precision. -1 if none specified */
int flag_sharp; /* True if the "#" flag is present */
int flag_plus; /* True if the "+" flag is present */
int flag_blank; /* True if the " " flag is present */
va_list ap; /* Pointer to the argument to be converted */
void *arg; /* Same as 3rd argument to converter() */
} convertctrl;
int converter(char, int (*)(convertctrl*), void *);
#endif
!end-of-xprintf.h!
More information about the Alt.sources
mailing list