home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Power-Programmierung
/
CD1.mdf
/
magazine
/
drdobbs
/
1990
/
08
/
mischel.lst
< prev
next >
Wrap
File List
|
1990-06-20
|
13KB
|
448 lines
_EXTENDING PRINTF()_
by Jim Mischel
[LISTING ONE]
/* mfmt.h -- macros and function prototypes for mfmt routine. */
#define printf AltPrintf
#define fprintf AltFprintf
#define sprintf AltSprintf
#define vprintf AltVprintf
#define vfprintf AltVfprintf
#define vsprintf AltVsprintf
int AltPrintf (char *fmt, ...);
int AltFprintf (FILE *f, char *fmt, ...);
int AltSprintf (char *Dest, char *fmt, ...);
int AltVprintf (char *fmt, va_list Arg);
int AltVfprintf (FILE *f, char *fmt, va_list Arg);
int AltVsprintf (char *Dest, char *fmt, va_list Arg);
[LISTING TWO]
/* mfmt.c -- function to output comma-separated numbers with printf().
* Copyright 1990, Jim Mischel
* To compile for testing:
* tcc -ms -f mfmt
* To compile for use as a called module:
* tcc -ms -c -f mfmt
*/
/* #define TESTING /* remove comment for testing */
#include "stdio.h"
#include "stdlib.h"
#include "ctype.h"
#include "string.h"
#include "math.h"
#define AddBlockArg(ap, type) (*((type *)(NewArgPtr))++) = va_arg(ap, type)
#define RemoveBlockArg(type) ((type *)(NewArgPtr))--
static va_list OldArgPtr; /* Pointer to passed arguments */
static va_list NewArgPtr; /* Pointer to new argument block */
static va_list NewBlock = NULL; /* New argument block */
static char *Sptr; /* Pointer to passed format string */
static char *Num; /* Formatted number */
static char *NumPtr; /* Pointer into formatted number string */
static char *t = NULL; /* New format string */
unsigned tSize; /* Length of new format string */
static char *Tptr; /* Pointer to new format string */
static char *SavePtr; /* Saved Tptr used when %m format found */
static int Flags; /* Flags identified in format string */
static int Width; /* Width from format string */
static int WidthFlag; /* Identifies width type */
static int Precision; /* Width from format string */
static int PrecisionFlag; /* Identifies precision type */
static int Size; /* printf() size modifier */
static int Sign; /* Sign of number for formatting */
/* Increment the flags counter for each occurance of a flag character.
* Valid flag characters are blank, '-', '#', '+'. */
/* Definitions for flag characters */
#define Blank 1
#define Dash 2
#define Pound 4
#define Plus 8
static void ParseFlags (void) {
Flags = 0;
while (*Sptr == ' ' || *Sptr == '-' || *Sptr == '#' || *Sptr == '+')
switch (*Tptr++ = *Sptr++) {
case ' ' : Flags += Blank; break;
case '-' : Flags += Dash; break;
case '#' : Flags += Pound; break;
case '+' : Flags += Plus; break;
} /* switch */
} /* ParseFlags */
/* Width specification. Minimum field width is returned in the Width variable.
* WidthFlag specifies if '0' or '*' was given in the format string. */
static void ParseWidth (void) {
Width = WidthFlag = 0;
if (*Sptr == '0')
WidthFlag = (*Tptr++ = *Sptr++);
if (*Sptr == '*') {
WidthFlag |= 0x80;
*Tptr++ = *Sptr++;
Width = (AddBlockArg(OldArgPtr, int));
if (Width < 0) {
Width = abs (Width);
Flags |= Dash;
}
}
while (isdigit (*Sptr)) {
Width = Width * 10 + *Sptr - '0';
*Tptr++ = *Sptr++;
}
} /* ParseWidth */
/* Precision specification. Returns precision in the variables Precision and
* PrecisionFlag.
* PrecisionFlag = 0, no precision was specified
* PrecisionFlag = '0', ".0" specified
* PrecisionFlag = 'n', ".n" specified
* PrecisionFlag = '*', ".*" specified
*/
static void ParsePrecision (void) {
Precision = PrecisionFlag = 0;
if (*Sptr == '.') { /* precision specified */
*Tptr++ = *Sptr++;
if (*Sptr == '*') {
PrecisionFlag = (*Tptr++ = *Sptr++);
Precision = (AddBlockArg(OldArgPtr, int));
}
else if (*Sptr == '0')
PrecisionFlag = (*Tptr++ = *Sptr++);
else {
PrecisionFlag = 'n';
while (isdigit (*Sptr)) {
Precision = Precision * 10 + *Sptr - '0';
*Tptr++ = *Sptr++;
}
}
}
} /* ParsePrecision */
/* Input size modifier. (F|N|h|l|L) */
static void ParseSize (void) {
if (*Sptr == 'F' || *Sptr == 'l' || *Sptr == 'L' ||
*Sptr == 'N' || *Sptr == 'h')
Size = (*Tptr++ = *Sptr++);
else
Size = 0;
} /* ParseSize */
/* dtoa -- convert a double to comma-separated ASCII representation. */
static void dtoa (double Work, int P, int Digits) {
int c;
if (P >= 0 || Work != 0) {
if (P == 0) {
c = '.';
Digits = 0;
P--;
}
else if (Digits == 3) {
c = ',';
Digits = 0;
}
else {
c = (int)(fmod (Work, 10))+'0';
modf (Work/10, &Work);
if (P > 0)
P--;
else
Digits++;
}
dtoa (Work, P, Digits);
*NumPtr++ = c;
}
} /* dtoa */
/* Right- or left- justifies the formatted number in the string.
* If the number is too large for the specified width, the number
* field is filled with the asterisk character (*). */
static void Justify (void) {
if ((WidthFlag & 0x7f) == '0') /* check width */
if (strlen (Num) > Width) {
memset (Num, '*', Width);
NumPtr = Num + Width;
*NumPtr = '\0';
return;
}
if (strlen (NumPtr) < Width) {
if (Flags & Dash) /* left justify */
memset (NumPtr, ' ', Width - strlen (Num));
else { /* right justify */
int n = Width - strlen (Num);
char *p = Num;
memmove (p + n, p, strlen (Num));
memset (p, ' ', n);
}
NumPtr = Num + Width;
*NumPtr = '\0';
}
} /* Justify */
/* Reports an out of memory error and aborts the program. */
static void MemoryError (char *s) {
fprintf (stderr, "Out of memory in function %s\n", s);
exit (1);
} /* MemoryError */
/* Format 'm' type into the new format string t. All parameters (flags, width,
* precision, size, and type) are stored in the respective variables. */
static void mFormat (void) {
double Work;
/* Move the block pointer back where it belongs if there were width or
* precision specifications in the argument list. */
*SavePtr = '\0';
if (PrecisionFlag == '*')
RemoveBlockArg (int);
if (WidthFlag & 0x80)
RemoveBlockArg (int);
if ((Num = malloc (128)) == NULL)
MemoryError ("mFormat");
NumPtr = Num;
/* The next argument in the list is the number that is to be formatted.
* This number will either be a float, a double, or a long double. The
* input size modifier will tell us what type. Whatever type it is, it will
* be copied into the double variable Work for us to work with. */
if (Size == 'l') Work = va_arg (OldArgPtr, double);
else if (Size == 'L') Work = (double) va_arg (OldArgPtr, long double);
else Work = (double) va_arg (OldArgPtr, float);
Sign = (Work < 0) ? -1 : 1;
if (!PrecisionFlag)
Precision = 2;
dtoa (floor (fabs (Work) * pow10 (Precision)),
(Precision == 0) ? -1 : Precision, 0);
*NumPtr = '\0';
/* The number is formatted into Num. Precision was handled by the dtoa()
* function. Add the sign and perform padding/justifying as necessary.
* Determine the proper sign */
if (Sign == -1) Sign = '-';
else if (Flags & Plus) Sign = '+';
else if (Flags & Blank) Sign = ' ';
else Sign = '\0';
if (Sign != '\0') /* Place sign */
if (Flags & Pound) { /* trailing sign */
*NumPtr++ = Sign;
*NumPtr = '\0';
}
else { /* leading sign */
memmove (Num+1, Num, (NumPtr - Num));
*Num = Sign;
NumPtr++;
}
Justify ();
/* Now re-allocate the string to add more characters and then append the
* newly-formatted number to the string and release the memory taken by
* the formatted number string. */
if ((t = realloc (t, (tSize += strlen (Num)))) == NULL)
MemoryError ("mFormat");
strcat (t, Num);
Tptr = t + strlen (t);
free (Num);
} /* mFormat */
/* Determine the conversion type. For any type but 'm', simply copy the passed
* argument to the new argument list and return. */
static void ParseType (void) {
switch (*Tptr++ = *Sptr++) {
case 'd' :
case 'i' :
case 'o' :
case 'u' :
case 'x' :
case 'X' :
case 'c' : /* in Turbo C, char is treated as int */
if (Size == 'l') AddBlockArg(OldArgPtr, long);
else AddBlockArg(OldArgPtr, int);
break;
case 'f' :
case 'e' :
case 'g' :
case 'E' :
case 'G' :
if (Size == 'l') AddBlockArg(OldArgPtr, double);
else if (Size == 'L') AddBlockArg(OldArgPtr, long double);
else AddBlockArg(OldArgPtr, float);
break;
/* In Turbo C, pointers to all types are the same size */
case 's' :
case 'n' :
case 'p' :
if (Size == 'F') AddBlockArg(OldArgPtr, char far *);
else if (Size == 'N') AddBlockArg(OldArgPtr, char near *);
else AddBlockArg(OldArgPtr, char *);
break;
case 'm' :
mFormat (); /* format 'm' type */
break;
default : /* anything else isn't defined */
break;
} /* switch */
} /* ParseType */
/* Parse a standard printf() format. */
static void ParseFormat (void) {
SavePtr = Tptr - 1;
ParseFlags ();
ParseWidth ();
ParsePrecision ();
ParseSize ();
ParseType ();
} /* ParseFormat */
/* Copy the input format string to the new format string t, replacing all %m
* formats with the formatted digit string. Arguments will be placed in the
* ParamBlock structure, with the %m parameters removed. */
static void Formatter (char *s, va_list Arg) {
Sptr = s;
/* Allocate memory for new format string. */
if ((t = malloc (tSize = strlen (s))) == NULL)
MemoryError ("Formatter");
Tptr = t;
OldArgPtr = Arg;
while (*Sptr != '\0')
if ((*Tptr++ = *Sptr++) == '%')
ParseFormat ();
va_end (OldArgPtr);
*Tptr = '\0';
} /* Formatter */
/* Allocate a block of memory for the modified argument list. */
static void InitBlock (char *s) {
int BlockSize = 0;
while (*s != '\0') /* count the '%' characters */
if (*s == '%') /* in the format string */
BlockSize++;
/* Multiply the number of '%' by the size of a long double, giving some
* idea of the maximum required size of the new parameter block. It's
* crude and implementation dependent, but it works. */
BlockSize *= sizeof (long double);
if ((NewBlock = malloc (BlockSize)) == NULL)
MemoryError ("InitBlock");
NewArgPtr = NewBlock;
} /* InitBlock */
/* Free the memory used by the modified argument list (if any) and the
* modified format string. */
static void FreeBlock (void) {
free (t);
free (NewBlock);
NewBlock = t = NULL;
} /* FreeBlock */
/* These 6 routines are the only functions visible to the application program.
* They are accessed though the printf, fprintf, sprintf, vprintf, vfprintf,
* and vsprintf macros, respectively. */
int AltPrintf (char *fmt, ...) {
va_list Arg;
int r;
va_start (Arg, fmt);
Formatter (fmt, NewArgPtr = Arg);
r = vprintf (t, Arg);
FreeBlock ();
return r;
} /* AltPrintf */
int AltFprintf (FILE *f, char *fmt, ...) {
va_list Arg;
int r;
va_start (Arg, fmt);
Formatter (fmt, NewArgPtr = Arg);
r = vfprintf (f, t, Arg);
FreeBlock ();
return r;
} /* AltFprintf */
int AltSprintf (char *Dest, char *fmt, ...) {
va_list Arg;
int r;
va_start (Arg, fmt);
Formatter (fmt, NewArgPtr = Arg);
r = vsprintf (Dest, t, Arg);
FreeBlock ();
return r;
} /* AltSprintf */
int AltVprintf (char *fmt, va_list Arg) {
int r;
InitBlock (fmt);
Formatter (fmt, Arg);
r = vprintf (t, NewBlock);
FreeBlock ();
return r;
} /* AltVprintf */
int AltVfprintf (FILE *f, char *fmt, va_list Arg) {
int r;
InitBlock (fmt);
Formatter (fmt, Arg);
r = vfprintf (f, t, NewBlock);
FreeBlock ();
return r;
} /* AltVfprintf */
int AltVsprintf (char *Dest, char *fmt, va_list Arg) {
int r;
InitBlock (fmt);
Formatter (fmt, Arg);
r = vsprintf (Dest, t, NewBlock);
FreeBlock ();
return r;
} /* AltVsprintf */
#ifdef TESTING
#include "mfmt.h"
void main (void) {
printf ("The national debt exceeds $%.0lm\n", (double)1000000000000.0);
}
#endif
[Figure 1: Stack contents for printf() and vprintf() calls that output the
information contained in the Person structure.]
struct Person {
char *Name;
int Age;
} Jim = {"Jim Mischel", 29};
(a) printf ("%s is %d years old.\n", Jim.Name, Jim.Age);
/-------------------\
| Jim.Age |
|-------------------|
| Jim.Name |
|-------------------|
| &(format string) |
|-------------------|
| return address |
\-------------------/
(b) vprintf ("%s is %d years old.\n", &Jim);
/-------------------\
| &Jim |
|-------------------|
| &(format string) |
|-------------------|
| return address |
\-------------------/