/smbase/gprintf.c
C | 407 lines | 311 code | 49 blank | 47 comment | 86 complexity | 11c40d65cd2c54266a5f1a9e5b32d0d6 MD5 | raw file
Possible License(s): BSD-3-Clause, GPL-2.0, LGPL-2.1
- /* gprintf.c */
- /* originally from: http://www.efgh.com/software/gprintf.htm */
- /* this file is in the public domain */
- /* modified by Scott McPeak, April 2003:
- * - use va_list instead of 'const int*' for the
- * pointer-to-argument type (for portability)
- * - implement conservative estimates for unknown format
- * chars, particularly 'f' (CONSERVATIVE_ESTIMATE flag)
- * - add a few test vectors
- */
- /* NOTE: There are quite a few differences among the *printf
- * implementations running around in the various libcs. The
- * implementation in this module doesn't know about all of those
- * variations and extensions. So, if you're using this to estimate
- * the # of chars your libc's printf will use, be sure to compare
- * libc's printf's actual return value, to make sure something doesn't
- * slip through the cracks. */
- /* Code for general_printf() */
- /* Change extension to .c before compiling */
- #include "gprintf.h" /* this module */
- #include <assert.h> /* assert */
- /* when this is true, unknown fields are filled with Xs, in an attempt
- * to print at least as many characters as libc's sprintf */
- #define CONSERVATIVE_ESTIMATE 1
- #define BITS_PER_BYTE 8
- struct parameters
- {
- int number_of_output_chars;
- short minimum_field_width;
- char options;
- #define MINUS_SIGN 1
- #define RIGHT_JUSTIFY 2
- #define ZERO_PAD 4
- #define CAPITAL_HEX 8
- short edited_string_length;
- short leading_zeros;
- int (*output_function)(void *, int);
- void *output_pointer;
- };
- static void output_and_count(struct parameters *p, int c)
- {
- if (p->number_of_output_chars >= 0)
- {
- int n = (*p->output_function)(p->output_pointer, c);
- if (n>=0) p->number_of_output_chars++;
- else p->number_of_output_chars = n;
- }
- }
- static void output_field(struct parameters *p, char const *s)
- {
- short justification_length =
- p->minimum_field_width - p->leading_zeros - p->edited_string_length;
- if (p->options & MINUS_SIGN)
- {
- if (p->options & ZERO_PAD)
- output_and_count(p, '-');
- justification_length--;
- }
- if (p->options & RIGHT_JUSTIFY)
- while (--justification_length >= 0)
- output_and_count(p, p->options & ZERO_PAD ? '0' : ' ');
- if (p->options & MINUS_SIGN && !(p->options & ZERO_PAD))
- output_and_count(p, '-');
- while (--p->leading_zeros >= 0)
- output_and_count(p, '0');
- while (--p->edited_string_length >= 0)
- output_and_count(p, *s++);
- while (--justification_length >= 0)
- output_and_count(p, ' ');
- }
- int general_vprintf(Gprintf_output_function output_function,
- void *output_pointer,
- const char *control_string,
- va_list argument_pointer)
- {
- struct parameters p;
- char control_char;
- p.number_of_output_chars = 0;
- p.output_function = output_function;
- p.output_pointer = output_pointer;
- control_char = *control_string++;
- while (control_char != '\0')
- {
- if (control_char == '%')
- {
- short precision = -1;
- short long_argument = 0;
- short base = 0;
- control_char = *control_string++;
- p.minimum_field_width = 0;
- p.leading_zeros = 0;
- p.options = RIGHT_JUSTIFY;
- if (control_char == '-')
- {
- p.options = 0;
- control_char = *control_string++;
- }
- if (control_char == '0')
- {
- p.options |= ZERO_PAD;
- control_char = *control_string++;
- }
- if (control_char == '*')
- {
- p.minimum_field_width = va_arg(argument_pointer, int);
- control_char = *control_string++;
- }
- else
- {
- while ('0' <= control_char && control_char <= '9')
- {
- p.minimum_field_width =
- p.minimum_field_width * 10 + control_char - '0';
- control_char = *control_string++;
- }
- }
- if (control_char == '.')
- {
- control_char = *control_string++;
- if (control_char == '*')
- {
- precision = va_arg(argument_pointer, int);
- control_char = *control_string++;
- }
- else
- {
- precision = 0;
- while ('0' <= control_char && control_char <= '9')
- {
- precision = precision * 10 + control_char - '0';
- control_char = *control_string++;
- }
- }
- }
- if (control_char == 'l')
- {
- long_argument = 1;
- control_char = *control_string++;
- }
- if (control_char == 'd')
- base = 10;
- else if (control_char == 'x')
- base = 16;
- else if (control_char == 'X')
- {
- base = 16;
- p.options |= CAPITAL_HEX;
- }
- else if (control_char == 'u')
- base = 10;
- else if (control_char == 'o')
- base = 8;
- else if (control_char == 'b')
- base = 2;
- else if (control_char == 'c')
- {
- base = -1;
- p.options &= ~ZERO_PAD;
- }
- else if (control_char == 's')
- {
- base = -2;
- p.options &= ~ZERO_PAD;
- }
- if (base == 0) /* invalid conversion type */
- {
- if (control_char != '\0')
- {
- #if !CONSERVATIVE_ESTIMATE
- /* sm: this was the original code; it just prints the
- * format character itself */
- output_and_count(&p, control_char);
- #else
- /* since my goal is actually to compute a conservative
- * upper bound on the # of chars output by sprintf, I want
- * to fill unknown fields with Xs */
- static char const * const XXX =
- "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; /* 50 Xs */
- assert(precision <= 30); /* otherwise I need more Xs */
- /* I'm assuming that printing floating-point is the worst case.
- * I further assume non-fractional parts (integer part,
- * exponent, decimal, sign) won't exceed 20 chars. Finally,
- * up to 30 characters of decimal part are supported (this
- * is checked with the assertion above). */
- if (precision == -1) {
- p.edited_string_length = 20 + 6; /* 6 is default precision for 'f' */
- }
- else {
- p.edited_string_length = 20 + precision;
- }
- output_field(&p, XXX);
- #endif
- control_char = *control_string++;
- }
- }
- else
- {
- if (base == -1) /* conversion type c */
- {
- /* 'char' is passed as 'int' through '...' */
- char c = (char)va_arg(argument_pointer, int);
- p.edited_string_length = 1;
- output_field(&p, &c);
- }
- else if (base == -2) /* conversion type s */
- {
- char *string;
- p.edited_string_length = 0;
- string = va_arg(argument_pointer, char*);
- while (string[p.edited_string_length] != 0)
- p.edited_string_length++;
- if (precision >= 0 && p.edited_string_length > precision)
- p.edited_string_length = precision;
- output_field(&p, string);
- }
- else /* conversion type d, b, o or x */
- {
- unsigned long x;
- char buffer[BITS_PER_BYTE * sizeof(unsigned long) + 1];
- p.edited_string_length = 0;
- if (long_argument)
- {
- x = va_arg(argument_pointer, unsigned long);
- }
- else if (control_char == 'd')
- x = va_arg(argument_pointer, long);
- else
- x = va_arg(argument_pointer, unsigned);
- if (control_char == 'd' && (long) x < 0)
- {
- p.options |= MINUS_SIGN;
- x = - (long) x;
- }
- do
- {
- int c;
- c = x % base + '0';
- if (c > '9')
- {
- if (p.options & CAPITAL_HEX)
- c += 'A'-'9'-1;
- else
- c += 'a'-'9'-1;
- }
- buffer[sizeof(buffer) - 1 - p.edited_string_length++] = c;
- }
- while ((x/=base) != 0);
- if (precision >= 0 && precision > p.edited_string_length)
- p.leading_zeros = precision - p.edited_string_length;
- output_field(&p, buffer + sizeof(buffer) - p.edited_string_length);
- }
- control_char = *control_string++;
- }
- }
- else
- {
- output_and_count(&p, control_char);
- control_char = *control_string++;
- }
- }
- return p.number_of_output_chars;
- }
- int general_printf(Gprintf_output_function output,
- void *extra, const char *format, ...)
- {
- va_list args;
- int ret;
- va_start(args, format);
- ret = general_vprintf(output, extra, format, args);
- va_end(args);
-
- return ret;
- }
- /* ------------------ test code --------------------- */
- #ifdef TEST_GPRINTF
- #include <stdio.h> /* fputc, printf, vsprintf */
- #include <string.h> /* strcmp, strlen */
- #include <stdlib.h> /* exit */
- int string_output(void *extra, int ch)
- {
- /* the 'extra' argument is a pointer to a pointer to the
- * next character to write */
- char **s = (char**)extra;
- **s = ch; /* write */
- (*s)++; /* advance */
- return 0;
- }
- int general_vsprintf(char *dest, char const *format, va_list args)
- {
- char *s = dest;
- int ret;
- ret = general_vprintf(string_output, &s, format, args);
- *s = 0;
- return ret;
- }
- char output1[1024]; /* for libc */
- char output2[1024]; /* for this module */
- void expect_vector_len(int expect_len, char const *expect_output,
- char const *format, va_list args)
- {
- int len;
- static int vectors = 0;
- /* keep track of how many vectors we've tried, to make it
- * a little easier to correlate failures with the inputs
- * in this file */
- vectors++;
- /* run the generalized vsprintf */
- len = general_vsprintf(output2, format, args);
- /* compare */
- if (len!=expect_len ||
- 0!=strcmp(expect_output, output2)) {
- printf("outputs differ for vector %d!\n", vectors);
- printf(" format: %s\n", format);
- printf(" expect: %s (%d)\n", expect_output, expect_len);
- printf(" me: %s (%d)\n", output2, len);
- exit(2);
- }
- }
- void expect_vector(char const *expect_output,
- char const *format, ...)
- {
- va_list args;
- va_start(args, format);
- expect_vector_len(strlen(expect_output), expect_output, format, args);
- va_end(args);
- }
- void vector(char const *format, ...)
- {
- va_list args;
- int len;
- /* run the real vsprintf */
- va_start(args, format);
- len = vsprintf(output1, format, args);
- va_end(args);
- /* test against the generalized vsprintf */
- va_start(args, format);
- expect_vector_len(len, output1, format, args);
- va_end(args);
- }
- int main()
- {
- printf("testing gprintf...\n");
- /* test against libc */
- vector("simple");
- vector("a %s more", "little");
- vector("some %4d more %s complicated %c stuff",
- 33, "yikes", 'f');
-
- /* test unknown format chars */
- expect_vector("XXXXXXXXXXXXXXXXXXXXXXXXXX", "%f", 3.4);
- expect_vector("XXXXXXXXXXXXXXXXXXXXXXX", "%.3f", 3.4);
- expect_vector("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "%.10f", 3.4);
- expect_vector("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "%.30f", 3.4);
- /* fails assertion, as it should */
- /* expect_vector("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "%.31f", 3.4); */
- /* TODO: add more tests */
- printf("gprintf works\n");
- return 0;
- }
- #endif /* TEST_GPRINTF */