home *** CD-ROM | disk | FTP | other *** search
/ Power-Programmierung / CD1.mdf / magazine / drdobbs / 1990 / 08 / mischel.lst < prev    next >
File List  |  1990-06-20  |  13KB  |  448 lines

  1. _EXTENDING PRINTF()_
  2. by Jim Mischel
  3.  
  4. [LISTING ONE]
  5.  
  6. /* mfmt.h -- macros and function prototypes for mfmt routine. */
  7.  
  8. #define printf   AltPrintf
  9. #define fprintf  AltFprintf
  10. #define sprintf  AltSprintf
  11. #define vprintf  AltVprintf
  12. #define vfprintf AltVfprintf
  13. #define vsprintf AltVsprintf
  14.  
  15. int AltPrintf   (char *fmt, ...);
  16. int AltFprintf  (FILE *f, char *fmt, ...);
  17. int AltSprintf  (char *Dest, char *fmt, ...);
  18. int AltVprintf  (char *fmt, va_list Arg);
  19. int AltVfprintf (FILE *f, char *fmt, va_list Arg);
  20. int AltVsprintf (char *Dest, char *fmt, va_list Arg);
  21.  
  22.  
  23.  
  24. [LISTING TWO]
  25.  
  26. /* mfmt.c -- function to output comma-separated numbers with printf().
  27.  * Copyright 1990, Jim Mischel
  28.  * To compile for testing:
  29.  *   tcc -ms -f mfmt
  30.  * To compile for use as a called module:
  31.  *   tcc -ms -c -f mfmt
  32.  */
  33. /* #define TESTING        /* remove comment for testing */
  34. #include "stdio.h"
  35. #include "stdlib.h"
  36. #include "ctype.h"
  37. #include "string.h"
  38. #include "math.h"
  39.  
  40. #define AddBlockArg(ap, type) (*((type *)(NewArgPtr))++) = va_arg(ap, type)
  41. #define RemoveBlockArg(type) ((type *)(NewArgPtr))--
  42.  
  43. static va_list OldArgPtr;    /* Pointer to passed arguments */
  44. static va_list NewArgPtr;    /* Pointer to new argument block */
  45. static va_list NewBlock = NULL;    /* New argument block */
  46. static char *Sptr;        /* Pointer to passed format string */
  47. static char *Num;        /* Formatted number */
  48. static char *NumPtr;        /* Pointer into formatted number string */
  49. static char *t = NULL;        /* New format string */
  50. unsigned tSize;            /* Length of new format string */
  51. static char *Tptr;        /* Pointer to new format string */
  52. static char *SavePtr;        /* Saved Tptr used when %m format found */
  53. static int Flags;        /* Flags identified in format string */
  54. static int Width;        /* Width from format string */
  55. static int WidthFlag;        /* Identifies width type */
  56. static int Precision;        /* Width from format string */
  57. static int PrecisionFlag;    /* Identifies precision type */
  58. static int Size;        /* printf() size modifier */
  59. static int Sign;        /* Sign of number for formatting */
  60.  
  61. /* Increment the flags counter for each occurance of a flag character.
  62.  * Valid flag characters are blank, '-', '#', '+'.  */
  63. /* Definitions for flag characters */
  64. #define Blank 1
  65. #define Dash 2
  66. #define Pound 4
  67. #define Plus 8
  68.  
  69. static void ParseFlags (void) {
  70.     Flags = 0;
  71.     while (*Sptr == ' ' || *Sptr == '-' || *Sptr == '#' || *Sptr == '+')
  72.     switch (*Tptr++ = *Sptr++) {
  73.         case ' ' : Flags += Blank; break;
  74.         case '-' : Flags += Dash; break;
  75.         case '#' : Flags += Pound; break;
  76.         case '+' : Flags += Plus; break;
  77.     } /* switch */
  78. } /* ParseFlags */
  79.  
  80. /* Width specification.  Minimum field width is returned in the Width variable.
  81.  * WidthFlag specifies if '0' or '*' was given in the format string.  */
  82. static void ParseWidth (void) {
  83.     Width = WidthFlag = 0;
  84.  
  85.     if (*Sptr == '0')
  86.     WidthFlag = (*Tptr++ = *Sptr++);
  87.     if (*Sptr == '*') {
  88.     WidthFlag |= 0x80;
  89.     *Tptr++ = *Sptr++;
  90.     Width = (AddBlockArg(OldArgPtr, int));
  91.     if (Width < 0) {
  92.         Width = abs (Width);
  93.         Flags |= Dash;
  94.     }
  95.     }
  96.     while (isdigit (*Sptr)) {
  97.     Width = Width * 10 + *Sptr - '0';
  98.     *Tptr++ = *Sptr++;
  99.     }
  100. } /* ParseWidth */
  101.  
  102. /* Precision specification. Returns precision in the variables Precision and 
  103.  * PrecisionFlag. 
  104.  * PrecisionFlag = 0, no precision was specified 
  105.  * PrecisionFlag = '0', ".0" specified
  106.  * PrecisionFlag = 'n', ".n" specified
  107.  * PrecisionFlag = '*', ".*" specified
  108.  */
  109. static void ParsePrecision (void) {
  110.     Precision = PrecisionFlag = 0;
  111.  
  112.     if (*Sptr == '.') {        /* precision specified */
  113.     *Tptr++ = *Sptr++;
  114.     if (*Sptr == '*') {
  115.         PrecisionFlag = (*Tptr++ = *Sptr++);
  116.         Precision = (AddBlockArg(OldArgPtr, int));
  117.     }
  118.     else if (*Sptr == '0')
  119.         PrecisionFlag = (*Tptr++ = *Sptr++);
  120.     else {
  121.         PrecisionFlag = 'n';
  122.         while (isdigit (*Sptr)) {
  123.         Precision = Precision * 10 + *Sptr - '0';
  124.         *Tptr++ = *Sptr++;
  125.         }
  126.     }
  127.     }
  128. } /* ParsePrecision */
  129.  
  130. /* Input size modifier.  (F|N|h|l|L) */
  131. static void ParseSize (void) {
  132.     if (*Sptr == 'F' || *Sptr == 'l' || *Sptr == 'L' ||
  133.     *Sptr == 'N' || *Sptr == 'h')
  134.     Size = (*Tptr++ = *Sptr++);
  135.     else
  136.     Size = 0;
  137. } /* ParseSize */
  138.  
  139. /* dtoa -- convert a double to comma-separated ASCII representation.  */
  140. static void dtoa (double Work, int P, int Digits) {
  141.     int c;
  142.  
  143.     if (P >= 0 || Work != 0) {
  144.     if (P == 0) {
  145.         c = '.';
  146.         Digits = 0;
  147.         P--;
  148.     }
  149.     else if (Digits == 3) {
  150.         c = ',';
  151.         Digits = 0;
  152.     }
  153.     else {
  154.         c = (int)(fmod (Work, 10))+'0';
  155.         modf (Work/10, &Work);
  156.         if (P > 0)
  157.         P--;
  158.         else
  159.         Digits++;
  160.     }
  161.     dtoa (Work, P, Digits);
  162.     *NumPtr++ = c;
  163.     }
  164. } /* dtoa */
  165.  
  166. /* Right- or left- justifies the formatted number in the string.
  167.  * If the number is too large for the specified width, the number
  168.  * field is filled with the asterisk character (*). */
  169. static void Justify (void) {
  170.     if ((WidthFlag & 0x7f) == '0')    /* check width */
  171.     if (strlen (Num) > Width) {
  172.         memset (Num, '*', Width);
  173.         NumPtr = Num + Width;
  174.         *NumPtr = '\0';
  175.         return;
  176.     }
  177.     if (strlen (NumPtr) < Width) {
  178.     if (Flags & Dash)     /* left justify */
  179.         memset (NumPtr, ' ', Width - strlen (Num));
  180.     else {            /* right justify */
  181.         int n = Width - strlen (Num);
  182.         char *p = Num;
  183.         memmove (p + n, p, strlen (Num));
  184.         memset (p, ' ', n);
  185.     }
  186.     NumPtr = Num + Width;
  187.     *NumPtr = '\0';
  188.     }
  189. } /* Justify */
  190.  
  191. /* Reports an out of memory error and aborts the program. */
  192. static void MemoryError (char *s) {
  193.     fprintf (stderr, "Out of memory in function %s\n", s);
  194.     exit (1);
  195. } /* MemoryError */
  196.  
  197. /* Format 'm' type into the new format string t. All parameters (flags, width,
  198.  * precision, size, and type) are stored in the respective variables.  */
  199. static void mFormat (void) {
  200.     double Work;
  201.     /* Move the block pointer back where it belongs if there were width or 
  202.      * precision specifications in the argument list. */
  203.     *SavePtr = '\0';
  204.     if (PrecisionFlag == '*')
  205.     RemoveBlockArg (int);
  206.     if (WidthFlag & 0x80)
  207.     RemoveBlockArg (int);
  208.  
  209.     if ((Num = malloc (128)) == NULL)
  210.     MemoryError ("mFormat");
  211.     NumPtr = Num;
  212.     /* The next argument in the list is the number that is to be formatted. 
  213.      * This number will either be a float, a double, or a long double. The 
  214.      * input size modifier will tell us what type. Whatever type it is, it will
  215.      * be copied into the double variable Work for us to work with. */
  216.     if (Size == 'l')      Work = va_arg (OldArgPtr, double);
  217.     else if (Size == 'L') Work = (double) va_arg (OldArgPtr, long double);
  218.     else                  Work = (double) va_arg (OldArgPtr, float);
  219.     Sign = (Work < 0) ? -1 : 1;
  220.     if (!PrecisionFlag)
  221.     Precision = 2;
  222.     dtoa (floor (fabs (Work) * pow10 (Precision)),
  223.       (Precision == 0) ? -1 : Precision, 0);
  224.     *NumPtr = '\0';
  225.     /* The number is formatted into Num. Precision was handled by the dtoa() 
  226.      * function. Add the sign and perform padding/justifying as necessary.
  227.      * Determine the proper sign */
  228.     if (Sign == -1)         Sign = '-';
  229.     else if (Flags & Plus)  Sign = '+';
  230.     else if (Flags & Blank) Sign = ' ';
  231.     else                    Sign = '\0';
  232.  
  233.     if (Sign != '\0')        /* Place sign */
  234.     if (Flags & Pound) {    /* trailing sign */
  235.         *NumPtr++ = Sign;
  236.         *NumPtr = '\0';
  237.     }
  238.     else {            /* leading sign */
  239.         memmove (Num+1, Num, (NumPtr - Num));
  240.         *Num = Sign;
  241.         NumPtr++;
  242.     }
  243.     Justify ();
  244.     /* Now re-allocate the string to add more characters and then append the 
  245.      * newly-formatted number to the string and release the memory taken by 
  246.      * the formatted number string. */
  247.     if ((t = realloc (t, (tSize += strlen (Num)))) == NULL)
  248.     MemoryError ("mFormat");
  249.     strcat (t, Num);
  250.     Tptr = t + strlen (t);
  251.     free (Num);
  252. } /* mFormat */
  253.  
  254. /* Determine the conversion type. For any type but 'm', simply copy the passed
  255.  * argument to the new argument list and return.  */
  256. static void ParseType (void) {
  257.     switch (*Tptr++ = *Sptr++) {
  258.     case 'd' :
  259.     case 'i' :
  260.     case 'o' :
  261.     case 'u' :
  262.     case 'x' :
  263.     case 'X' :
  264.     case 'c' :        /* in Turbo C, char is treated as int */
  265.         if (Size == 'l') AddBlockArg(OldArgPtr, long);
  266.         else             AddBlockArg(OldArgPtr, int);
  267.         break;
  268.     case 'f' :
  269.     case 'e' :
  270.     case 'g' :
  271.     case 'E' :
  272.     case 'G' :
  273.         if (Size == 'l')       AddBlockArg(OldArgPtr, double);
  274.         else if (Size == 'L') AddBlockArg(OldArgPtr, long double);
  275.         else                  AddBlockArg(OldArgPtr, float);
  276.         break;
  277.     /* In Turbo C, pointers to all types are the same size */
  278.     case 's' :
  279.     case 'n' :
  280.     case 'p' :
  281.         if (Size == 'F')      AddBlockArg(OldArgPtr, char far *);
  282.         else if (Size == 'N') AddBlockArg(OldArgPtr, char near *);
  283.         else                  AddBlockArg(OldArgPtr, char *);
  284.         break;
  285.     case 'm' :
  286.         mFormat ();        /* format 'm' type */
  287.         break;
  288.     default     :        /* anything else isn't defined */
  289.         break;
  290.     } /* switch */
  291. } /* ParseType */
  292.  
  293. /* Parse a standard printf() format.  */
  294. static void ParseFormat (void) {
  295.  
  296.     SavePtr = Tptr - 1;
  297.  
  298.     ParseFlags ();
  299.     ParseWidth ();
  300.     ParsePrecision ();
  301.     ParseSize ();
  302.     ParseType ();
  303. } /* ParseFormat */
  304.  
  305. /* Copy the input format string to the new format string t, replacing all %m 
  306.  * formats with the formatted digit string. Arguments will be placed in the 
  307.  * ParamBlock structure, with the %m parameters removed. */
  308. static void Formatter (char *s, va_list Arg) {
  309.     Sptr = s;
  310.     /* Allocate memory for new format string. */
  311.     if ((t = malloc (tSize = strlen (s))) == NULL)
  312.     MemoryError ("Formatter");
  313.     Tptr = t;
  314.     OldArgPtr = Arg;
  315.     while (*Sptr !=  '\0')
  316.     if ((*Tptr++ = *Sptr++) == '%')
  317.         ParseFormat ();
  318.     va_end (OldArgPtr);
  319.     *Tptr = '\0';
  320. } /* Formatter */
  321.  
  322. /* Allocate a block of memory for the modified argument list.  */
  323. static void InitBlock (char *s) {
  324.     int BlockSize = 0;
  325.  
  326.     while (*s != '\0')        /* count the '%' characters */
  327.     if (*s == '%')        /* in the format string */
  328.         BlockSize++;
  329.     /* Multiply the number of '%' by the size of a long double, giving some 
  330.      * idea of the maximum required size of the new parameter block. It's 
  331.      * crude and implementation dependent, but it works. */
  332.     BlockSize *= sizeof (long double);
  333.     if ((NewBlock = malloc (BlockSize)) == NULL)
  334.     MemoryError ("InitBlock");
  335.     NewArgPtr = NewBlock;
  336. } /* InitBlock */
  337.  
  338. /* Free the memory used by the modified argument list (if any) and the
  339.  * modified format string.  */
  340. static void FreeBlock (void) {
  341.     free (t);
  342.     free (NewBlock);
  343.     NewBlock = t = NULL;
  344. } /* FreeBlock */
  345.  
  346. /* These 6 routines are the only functions visible to the application program. 
  347.  * They are accessed though the printf, fprintf, sprintf, vprintf, vfprintf, 
  348.  * and vsprintf macros, respectively.  */
  349. int AltPrintf (char *fmt, ...) {
  350.     va_list Arg;
  351.     int r;
  352.     va_start (Arg, fmt);
  353.     Formatter (fmt, NewArgPtr = Arg);
  354.     r = vprintf (t, Arg);
  355.     FreeBlock ();
  356.     return r;
  357. } /* AltPrintf */
  358.  
  359. int AltFprintf (FILE *f, char *fmt, ...) {
  360.     va_list Arg;
  361.     int r;
  362.     va_start (Arg, fmt);
  363.     Formatter (fmt, NewArgPtr = Arg);
  364.     r = vfprintf (f, t, Arg);
  365.     FreeBlock ();
  366.     return r;
  367. } /* AltFprintf */
  368.  
  369. int AltSprintf (char *Dest, char *fmt, ...) {
  370.     va_list Arg;
  371.     int r;
  372.     va_start (Arg, fmt);
  373.     Formatter (fmt, NewArgPtr = Arg);
  374.     r = vsprintf (Dest, t, Arg);
  375.     FreeBlock ();
  376.     return r;
  377. } /* AltSprintf */
  378.  
  379. int AltVprintf (char *fmt, va_list Arg) {
  380.     int r;
  381.     InitBlock (fmt);
  382.     Formatter (fmt, Arg);
  383.     r = vprintf (t, NewBlock);
  384.     FreeBlock ();
  385.     return r;
  386. } /* AltVprintf */
  387.  
  388. int AltVfprintf (FILE *f, char *fmt, va_list Arg) {
  389.     int r;
  390.     InitBlock (fmt);
  391.     Formatter (fmt, Arg);
  392.     r = vfprintf (f, t, NewBlock);
  393.     FreeBlock ();
  394.     return r;
  395. } /* AltVfprintf */
  396.  
  397. int AltVsprintf (char *Dest, char *fmt, va_list Arg) {
  398.     int r;
  399.     InitBlock (fmt);
  400.     Formatter (fmt, Arg);
  401.     r = vsprintf (Dest, t, NewBlock);
  402.     FreeBlock ();
  403.     return r;
  404. } /* AltVsprintf */
  405.  
  406. #ifdef TESTING
  407. #include "mfmt.h"
  408. void main (void) {
  409.     printf ("The national debt exceeds $%.0lm\n", (double)1000000000000.0);
  410. }
  411. #endif
  412.  
  413.  
  414.  
  415.  
  416. [Figure 1:  Stack contents for printf() and vprintf() calls that output the 
  417. information contained in the Person structure.]
  418.  
  419.     struct Person {
  420.         char *Name;
  421.         int Age;
  422.     } Jim = {"Jim Mischel", 29};
  423.  
  424. (a) printf ("%s is %d years old.\n", Jim.Name, Jim.Age);
  425.  
  426. /-------------------\
  427. |    Jim.Age        |
  428. |-------------------|
  429. |    Jim.Name       |
  430. |-------------------|
  431. | &(format string)  |
  432. |-------------------|
  433. |   return address  |
  434. \-------------------/
  435.  
  436. (b) vprintf ("%s is %d years old.\n", &Jim);
  437.  
  438. /-------------------\
  439. |      &Jim         |
  440. |-------------------|
  441. | &(format string)  |
  442. |-------------------|
  443. |   return address  |
  444. \-------------------/
  445.  
  446.  
  447.  
  448.