home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The C Users' Group Library 1994 August
/
wc-cdrom-cusersgrouplibrary-1994-08.iso
/
listings
/
v_08_09
/
8n09039a
< prev
next >
Wrap
Text File
|
1990-07-21
|
16KB
|
546 lines
/* > DPRINTF.C
* dprintf -- Source Code
* (C) August 30 1989 Arkin Asaf
* All rights reserved
* References:
* C: A Reference Manual/Chapter 17, pp 328-340
* The Waite Group's Guide to ANSI C/Chapter 7, pp 84-87 */
/* Include files: */
#include <ctype.h>
#include <setjmp.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Macro constants:- TRUE/FALSE, Flags mask bits, and
* argument size types.
* Macros:- Maximum and Minimum expand to yield the maximum
* or minimum of two expressions; ToValue gives the decimal
* value of an ASCII digit and ToDigit returns a digit from
* a value in any radix. N.B.: It goes without saying that
* the macro parameters must contain no operations of con-
* sequence, for they will be carried out more than once. */
#define TRUE 1
#define FALSE 0
#define MaskJustify 0x01 /* - Left justify value within
* field */
#define MaskPlusSign 0x02 /* + Precede positive value
* with plus */
#define MaskSpace 0x04 /* sp Precede positive value
* with space */
#define MaskZeros 0x08 /* 0 Justify value with zeros */
#define MaskVarient 0x10 /* # Output value in varient
* format */
#define TypeNormal 1 /* int/double */
#define TypeShort 2 /* short (meaningless) */
#define TypeLong 3 /* long int */
#define TypeDouble 4 /* long double */
#define Maximum(a,b) ((a)>(b)?(a):(b))
#define Minimum(a,b) ((a)<(b)?(a):(b))
#define ToValue(a) ((a)-'0')
#define ToDigit(a) ((a)<10?(a)+'0':(a)-10+'A')
/* OutFunc (of type dprintf_fp) points to a putchar-like
* function, which performs all output. Called with a
* character int as parameter, the function returns EOF
* only if an output error occured.*/
typedef int (*dprintf_fp)(int);
static dprintf_fp OutFunc;
/* Function declarations: */
int dprintf(dprintf_fp, const char *, ...);
int vdprintf(dprintf_fp, const char *, va_list);
static void PrintDecimal(long, int, int, char, int *);
static void PrintRadix(unsigned long, int, int, char, char,
int *);
static void PrintFloat(long double, int, int, char, char,
int *);
static void Print(char *, char *, int, int, char, int *);
static int ToInteger(char **, unsigned long, int, int);
static void dputc(int);
/* dputc employs this longjmp buffer in the event of an
* output error. */
jmp_buf dputc_Buf;
/* int dprintf(dprintf_fp, const char *, ...)
* dprintf accepts pointers to a putchar-like function and
* a format string. It then passes them to vdprintf, along
* with a pointer to the variable arguments list. */
int dprintf(dprintf_fp Func, const char *Format, ...)
{
int Return;
va_list Args;
va_start(Args,Format);
Return=vdprintf(Func,Format,Args);
va_end(Args);
return Return;
}
/* int vdprintf(dprintf_fp, const char *, va_list)
* vdprintf is an implementation of vprintf, as defined in
* the ANSI standard, with an additional
* pointer-to-function as its first parameter. On exit,
* vdprintf returns the number of characters successfully
* printed, EOF if an error occured. */
int vdprintf(dprintf_fp Func, const char *Format,
va_list Args)
{
char Flags, Size, *Ptr;
int Width, Precis, OutCnt = 0;
long Int;
unsigned long UnsgInt;
long double Float;
char *FlagsList = "-+ 0#", *TypesList = "hlL";
/* The pointer-to-function assigns to static variable
* OutFunc, rather than being passed through three layers
* of functions. The longjmp buffer is then initialized,
* so dputc can return in case of an output error. */
OutFunc=Func;
if (setjmp(dputc_Buf))
return EOF;
/* The format string is scanned a character at a time:
* %'s are processed, all other characters are merely
* echoed to the output. */
for (; *Format; ++Format)
{
if (*Format!='%')
{
dputc(*Format);
++OutCnt;
continue;
}
/* An output format can start with a combination of
* five flags: - + spc 0 #. Flags is set accordingly. */
if (!*++Format)
return EOF;
Flags=0;
while ((Ptr=strchr(FlagsList,*Format))!=NULL)
{
Flags|=1<<(Ptr-FlagsList);
++Format;
}
/* Read width (zero assumed, if absent) and precision
* (-1 assumed, if absent): width must not start with
* a zero; precision precedes with a period -- if no
* precision follows the period, zero is assumed; an
* int argument is consumed for the width or precision,
* if an * replaces the value of either. */
Width=0;
if (*Format=='*')
{
Width=va_arg(Args,int);
++Format;
}
else
while (isdigit(*Format))
Width=Width*10+ToValue(*Format++);
if (*Format=='.')
{
Precis=0;
if (*++Format=='*')
{
Precis=va_arg(Args,int);
++Format;
}
else
while (isdigit(*Format))
Precis=Precis*10+ToValue(*Format++);
}
else
Precis=-1;
/* An argument is either int (default), short int ('h'
* -- meaningless), long int ('l'), double (default
* float), or long double ('L'). */
if ((Ptr=strchr(TypesList,*Format))!=NULL)
{
Size=Ptr-TypesList+TypeShort;
++Format;
}
else
Size=TypeNormal;
/* Consume one output format letter.
* Auxiliary functions process most formats, keeping
* vdprintf short, or else it may fail to compile. */
switch (*Format)
{
case 'd':
case 'i':
if (Size==TypeLong)
Int=va_arg(Args,long);
else
Int=va_arg(Args,int);
PrintDecimal(Int,Width,Precis,Flags,&OutCnt);
break;
case 'u':
case 'o':
case 'x': case 'X':
if (Size==TypeLong)
UnsgInt=va_arg(Args,unsigned long);
else
UnsgInt=va_arg(Args,unsigned);
PrintRadix(UnsgInt,Width,Precis,Flags,*Format,
&OutCnt);
break;
case 'c':
{
static char Char[2]= {0,0};
Char[0]=va_arg(Args,unsigned char);
Print(Char,NULL,Width,-1,Flags,&OutCnt);
break;
}
case 's':
Ptr=va_arg(Args,char *);
Print(Ptr,NULL,Width,Precis,Flags,&OutCnt);
break;
case 'f':
case 'e': case 'E':
case 'g': case 'G':
if (Size==TypeDouble)
Float=va_arg(Args,long double);
else
Float=va_arg(Args,double);
PrintFloat(Float,Width,Precis,Flags,*Format,&OutCnt);
break;
case 'p':
/* The pointer-to-void argument is cast to long
* unsigned, assuming pointer representation to
* remain intact. */
UnsgInt=(unsigned long) va_arg(Args,void *);
PrintRadix(UnsgInt,Width,Precis,Flags,*Format,
&OutCnt);
break;
case 'n':
*(va_arg(Args,int *))=OutCnt;
break;
case '%':
Print("%",NULL,Width,-1,Flags,&OutCnt);
break;
default:
return EOF;
}
}
return OutCnt;
}
/* void PrintDecimal(long, int, int, char, int *)
* Print a decimal value (%d or %i) with a sign prefix. */
static void PrintDecimal(long Int, int Width, int Precis,
char Flags, int *OutCnt)
{
char *Prefix;
char *Buffer;
if(Int<0)
{
Int=-Int;
Prefix="-";
}
else
{
if (Flags&MaskPlusSign)
Prefix="+";
else
if (Flags&MaskSpace)
Prefix=" ";
else
Prefix=NULL;
}
ToInteger(&Buffer,Int,10,Precis);
Print(Buffer,Prefix,Width,-1,Flags,OutCnt);
free(Buffer);
}
/* void PrintRadix(unsigned long, int, int, char, char,
* int *) Print an unsigned int in decimal (%u), octal
* (%o), hexadecimal (%x or %X), or pointer (%p) form. In
* the varient format octals prefix with a 0, hexadecimals
* with a 0x, and pointers with an @. (Hexadecimal letters
* are in the same case as is the format letter.) */
static void PrintRadix(unsigned long Int, int Width, int
Precis, char Flags, char Format,
int *OutCnt)
{
char *Prefix = NULL;
char *Buffer;
int Length;
if (Format=='u')
{
ToInteger(&Buffer,Int,10,Precis);
Print(Buffer,NULL,Width,-1,Flags,OutCnt);
}
else
if (Format=='o')
{
if (Flags&MaskVarient)
Prefix="0";
ToInteger(&Buffer,Int,8,Precis);
Print(Buffer,Prefix,Width,-1,Flags,OutCnt);
}
else
if (Format=='p')
{
/* Various architectures impose different pointer rep-
* resentations, both in memory and in writing. As is,
* an 8-digit hexadecimal number prints (upper case
* letters), prefixed with an @ in the varient format. */
if (Flags&MaskVarient)
Prefix="@";
ToInteger(&Buffer,Int,16,8);
Print(Buffer,Prefix,Width,-1,Flags,OutCnt);
}
else
{
if (Flags&MaskVarient)
Prefix=(Format=='x')?"0x":"0X";
Length=ToInteger(&Buffer,Int,16,Precis);
if (Format=='x')
{
for (; Length>0; --Length)
Buffer[Length-1]=tolower(Buffer[Length-1]);
}
Print(Buffer,Prefix,Width,-1,Flags,OutCnt);
}
free(Buffer);
}
/* void PrintFloat(long double, int, int, char, char,
* int *) Print a floating point number in standard (%f) or
* engineering (%e) form; the %g format requires that the
* shortest of the two be selected. The number divides into
* integer, fraction and exponent parts; each is cast into
* a long int, stringized with ToInteger, and Printed. */
static void PrintFloat(long double Float, int Width, int
Precis, char Flags, char Format,
int *OutCnt)
{
char *Prefix;
char *BufferI, *BufferF = NULL, *BufferE = NULL;
int LengthI, LengthF, LengthE = 0;
int Short = (Format=='g' || Format=='G');
int Exponent = 0;
unsigned long Int;
/* Determine prefix according to sign and format flags.
* If no precision was given, six is assumed. */
if(Float<0)
{
Float=-Float;
Prefix="-";
}
else
{
if (Flags&MaskPlusSign)
Prefix="+";
else
if (Flags&MaskSpace)
Prefix=" ";
else
Prefix=NULL;
}
if (Precis<0)
Precis=6;
if (Format=='e' || Format=='E' || Short)
{
long double TempFloat = Float;
/* For %e and %g formats, establish the exponent:
* Float is divided and multiplied by ten, until it's
* value rests between zero and one. Exponent totals
* all divisions minus all multiplications. */
if (Float!=0)
{
while (Float>10)
{
Float/=10;
++Exponent;
}
while (Float<1)
{
Float*=10;
--Exponent;
}
}
LengthE=ToInteger(&BufferE,Exponent,10,2)+2;
/* If the %f format is shorter, %g requires that
* the exponent be cancelled and that amount of
* precision be lost; It states that one precision
* digit be lost in any case. */
if (Short)
{
if (Precis>0)
--Precis;
if (Exponent>=-3 && Exponent<=Precis)
{
LengthE=0;
Precis-=Exponent;
Float=TempFloat;
}
}
}
/* The mantissa divides into integer and fraction parts,
* stringized by ToInteger: the last digit always rounds
* up; a period is printed only before a fraction or in
* the varient format; the %g format allows trailing zeros
* in the fraction to be lost. N.B.: Too long floating
* numbers may raise an exception on conversion to long
* int, or otherwise fail to convert properly. */
Int=(unsigned long) Float;
Float-=(long double) Int;
if (Precis<=0 && Float>=.5)
++Int;
LengthI=ToInteger(&BufferI,Int,10,-1);
if (Precis>0)
{
for (LengthF=0; LengthF<Precis; ++LengthF)
Float*=10;
Int=(unsigned long) Float;
Float-=(long double) Int;
if (Float>=.5)
++Int;
LengthF=ToInteger(&BufferF,Int,10,Precis);
}
else
LengthF=0;
if (Short && !(Flags&MaskVarient))
while (LengthF>0 && BufferF[LengthF-1]=='0')
--LengthF;
if (Flags&MaskVarient || LengthF>0)
--Width;
Width-=LengthF+LengthE;
Print(BufferI,Prefix,Width,-1,Flags,OutCnt);
if (Flags&MaskVarient || LengthF>0)
{
dputc('.');
++*OutCnt;
}
if (LengthF>0)
Print(BufferF,NULL,LengthF,LengthF,MaskZeros,OutCnt);
/* Print exponent part of number, with an 'e' in the same
* case as is the format letter. Exponents must have a
* sign and at least two digits. */
if (LengthE>0)
{
if (Format=='g')
Format='e';
else
if (Format=='G')
Format='E';
dputc(Format);
if (Exponent<0)
{
Exponent=-Exponent;
Prefix="-";
}
else
Prefix="+";
Print(BufferE,Prefix,3,-1,MaskZeros,OutCnt);
}
free(BufferI);
free(BufferF);
free(BufferE);
}
/* int ToInteger(char **, unsigned long, int, int)
*
* Convert an unsigned int to a NULL-terminated string of
* digits in the given radix. If the string has less digits
* than the precision, additional zeros are inserted at the
* start of it. ToInteger allocates a memory block in which
* it stores the string -- Buffer returns its address. */
static int ToInteger(char **Buffer, unsigned long Int,
int Radix, int Precis)
{
int Cnt, Length;
unsigned long TempInt = Int;
if (Precis<0)
Precis=1;
for (Cnt=0; TempInt!=0; TempInt/=Radix, ++Cnt) ;
*Buffer=malloc(Maximum(Precis,Cnt)+1);
if (*Buffer==NULL)
return 0;
for (Length=0; Length+Cnt<Precis; )
(*Buffer)[Length++]='0';
Cnt= Length=Maximum(Length+Cnt,Precis);
(*Buffer)[Length]='\0';
for (; Int>0; Int/=Radix)
(*Buffer)[--Cnt]=ToDigit(Int%Radix);
return Length;
}
/* void Print(char *, char *, int, int, char, int *)
* Print prefix followed by value, incrementing OutCnt by
* the total number of characters printed: by default, the
* string is right justified within the field with spaces;
* the 0 flag places zeros between prefix and value; and
* the - flag left justifies by appending spaces at the
* end. Note that if Maximum is not -1, no more than that
* number of characters are printed. */
static void Print(char *String, char *Prefix, int Width,
int Maximum, char Flags, int *OutCnt)
{
int Length = strlen(String);
if (Prefix)
Length+=strlen(Prefix);
if (Maximum>=0 && Length>Maximum)
Length=Maximum;
*OutCnt+=Maximum(Length,Width);
if (!(Flags&MaskJustify || Flags&MaskZeros))
for (; Length<Width; --Width)
dputc(' ');
while (Prefix && *Prefix!='\0')
{
dputc(*Prefix++);
--Length;
--Width;
}
if (!(Flags&MaskJustify))
for (; Length<Width; --Width)
dputc('0');
while (*String!='\0' && Length>0)
{
dputc(*String++);
--Length;
--Width;
}
for (; Width>0; --Width)
dputc(' ');
}
/* void dputc(int)
* Perform character output through the putchar-like
* function provided. If it returns EOF, a longjmp to
* vdprintf will make it return EOF to its caller. */
static void dputc(int Char)
{
if (OutFunc(Char)==EOF)
longjmp(dputc_Buf,EOF);
}