home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The C Users' Group Library 1994 August
/
wc-cdrom-cusersgrouplibrary-1994-08.iso
/
vol_300
/
329_01
/
dprintf.c
< prev
next >
Wrap
Text File
|
1979-12-31
|
16KB
|
588 lines
/* > DPRINTF.C
*
* dprintf -- Source Code
* (C) April 4 1990 Asaf Arkin
* All rights reserved
*/
/* 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 have no side
* effects, as 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 MaskVariant 0x10 /* # Output value in variant 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 as parameter through three layers of functions. The
* longjmp buffer is then initialized, so dputc can return in the event 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);
if (Width<0)
{
Width=-Width;
Flags|=MaskJustify;
}
++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};
/* chars are promoted to int when passed to functions.
*/
Char[0]=va_arg(Args,int);
Print(Char,NULL,Width,-1,Flags,&OutCnt);
break;
}
case 's':
/* If pointer is null, print string "{NULL}".
*/
Ptr=va_arg(Args,char *);
if (Ptr==NULL)
Ptr="{NULL}";
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) representation. In the variant 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&MaskVariant && Int>0)
{
Prefix="0";
Precis-=1;
}
ToInteger(&Buffer,Int,8,Precis);
Print(Buffer,Prefix,Width,-1,Flags,OutCnt);
}
else
if (Format=='p')
{
/* Various architectures impose different pointer representations, both
* in memory and in writing. Default format is: HHHH:HHHH
* (two words, upper case hexadecimal digits), prefixed with an @ in
* the variant format.
*/
if (Flags&MaskVariant)
Prefix="@";
ToInteger(&Buffer,Int>>16, 16,4);
Print(Buffer,Prefix,Width-5,-1,Flags,OutCnt);
ToInteger(&Buffer,Int&0xffff,16,4);
Print(Buffer,":",5,-1,0,OutCnt);
}
else
{
if (Flags&MaskVariant)
{
Prefix=(Format=='x')?"0x":"0X";
Precis-=2;
}
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 Print-ed.
*/
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, a precision 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<0?-Exponent: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>=-4 && 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 variant 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&MaskVariant))
while (LengthF>0 && BufferF[LengthF-1]=='0')
--LengthF;
if (Flags&MaskVariant || LengthF>0)
--Width;
Width-=LengthF+LengthE;
Print(BufferI,Prefix,Width,-1,Flags,OutCnt);
if (Flags&MaskVariant || 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 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 cause the latter to return EOF
* to its caller.
*/
static void dputc(int Char)
{
if (OutFunc(Char)==EOF)
longjmp(dputc_Buf,EOF);
}