From a04e740593c921d45de28bb5ccafe46f9f772769 Mon Sep 17 00:00:00 2001 From: Roland McGrath Date: Fri, 24 Mar 1995 07:44:08 +0000 Subject: Fri Mar 24 02:35:37 1995 Roland McGrath * stdio/printf-parse.h: New file, mostly written by drepper. * stdio/vfprintf.c: Rewritten, mostly by drepper. * stdio/printf-prs.c: Rewritten. * stdio/Makefile (distribute): Add printf-parse.h. Thu Mar 23 22:03:44 1995 Roland McGrath * sysdeps/unix/start.c [! NO_UNDERSCORES]: Don't declare _start with asm name. Just do a ".set start, __start". * malloc/realloc.c: Call _free_internal instead of free. * stdlib/Makefile: All the mpn stuff moved here from stdio/Makefile. --- stdio/Makefile | 2 +- stdio/printf-parse.h | 388 +++++++++++++++++++ stdio/printf-prs.c | 205 ++--------- stdio/vfprintf.c | 1002 ++++++++++++++++++++++++-------------------------- 4 files changed, 895 insertions(+), 702 deletions(-) create mode 100644 stdio/printf-parse.h (limited to 'stdio') diff --git a/stdio/Makefile b/stdio/Makefile index 64b0598111..955d7f1fcd 100644 --- a/stdio/Makefile +++ b/stdio/Makefile @@ -48,7 +48,7 @@ routines := \ memstream obstream \ internals sysd-stdio pipestream stdio_init libc_fatal aux := errlist siglist defs glue -distribute := _itoa.h +distribute := _itoa.h printf-parse.h tests := tst-printf tstscanf test_rdwr test-popen tstgetln test-fseek \ temptest tst-fileno test-fwrite \ diff --git a/stdio/printf-parse.h b/stdio/printf-parse.h new file mode 100644 index 0000000000..0f6e9e287b --- /dev/null +++ b/stdio/printf-parse.h @@ -0,0 +1,388 @@ +/* Internal header for parsing printf format strings. +Copyright (C) 1995 Free Software Foundation, Inc. +This file is part of th GNU C Library. + +The GNU C Library is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public License as +published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version. + +The GNU C Library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public +License along with the GNU C Library; see the file COPYING.LIB. If +not, write to the Free Software Foundation, Inc., 675 Mass Ave, +Cambridge, MA 02139, USA. */ + +#include +#include +#include +#include + +#define NDEBUG 1 +#include + +#define MAX(a,b) ({typeof(a) _a = (a); typeof(b) _b = (b); \ + _a > _b ? _a : _b; }) +#define MIN(a,b) ({typeof(a) _a = (a); typeof(b) _b = (b); \ + _a < _b ? _a : _b; }) + +struct printf_spec + { + /* Information parsed from the format spec. */ + struct printf_info info; + + /* Pointers into the format string for the end of this format + spec and the next (or to the end of the string if no more). */ + const char *end_of_fmt, *next_fmt; + + /* Position of arguments for precision and width, or -1 if `info' has + the constant value. */ + int prec_arg, width_arg; + + int data_arg; /* Position of data argument. */ + int data_arg_type; /* Type of first argument. */ + /* Number of arguments consumed by this format specifier. */ + size_t ndata_args; + }; + + +/* The various kinds off arguments that can be passed to printf. */ +union printf_arg + { + unsigned char pa_char; + short int pa_short_int; + int pa_int; + long int pa_long_int; + long long int pa_long_long_int; + unsigned short int pa_u_short_int; + unsigned int pa_u_int; + unsigned long int pa_u_long_int; + unsigned long long int pa_u_long_long_int; + float pa_float; + double pa_double; + long double pa_long_double; + const char *pa_string; + void *pa_pointer; + }; + + +/* Read a simple integer from a string and update the string pointer. + It is assumed that the first character is a digit. */ +static inline unsigned int +read_int (const char * *pstr) +{ + unsigned int retval = **pstr - '0'; + + while (isdigit (*++(*pstr))) + { + retval *= 10; + retval += **pstr - '0'; + } + + return retval; +} + + + +/* Find the next spec in FORMAT, or the end of the string. Returns + a pointer into FORMAT, to a '%' or a '\0'. */ +static inline const char * +find_spec (const char *format) +{ + while (*format != '\0' && *format != '%') + { + int len; + + if (isascii (*format) || (len = mblen (format, MB_CUR_MAX)) <= 0) + ++format; + else + format += len; + } + return format; +} + + +/* This is defined in reg-printf.c. */ +extern printf_arginfo_function **__printf_arginfo_table; + + +/* FORMAT must point to a '%' at the beginning of a spec. Fills in *SPEC + with the parsed details. POSN is the number of arguments already + consumed. At most MAXTYPES - POSN types are filled in TYPES. Return + the number of args consumed by this spec; *MAX_REF_ARG is updated so it + remains the highest argument index used. */ +static inline size_t +parse_one_spec (const char *format, size_t posn, struct printf_spec *spec, + size_t *max_ref_arg) +{ + unsigned int n; + size_t nargs = 0; + + /* Skip the '%'. */ + ++format; + + /* Clear information structure. */ + spec->data_arg = -1; + spec->info.alt = 0; + spec->info.space = 0; + spec->info.left = 0; + spec->info.showsign = 0; + spec->info.group = 0; + spec->info.pad = ' '; + + /* Test for positional argument. */ + if (isdigit (*format)) + { + const char *begin = format; + + n = read_int (&format); + + if (n > 0 && *format == '$') + /* Is positional parameter. */ + { + ++format; /* Skip the '$'. */ + spec->data_arg = n - 1; + *max_ref_arg = MAX (*max_ref_arg, n); + } + else + /* Oops; that was actually the width and/or 0 padding flag. + Step back and read it again. */ + format = begin; + } + + /* Check for spec modifiers. */ + while (*format == ' ' || *format == '+' || *format == '-' || + *format == '#' || *format == '0' || *format == '\'') + switch (*format++) + { + case ' ': + /* Output a space in place of a sign, when there is no sign. */ + spec->info.space = 1; + break; + case '+': + /* Always output + or - for numbers. */ + spec->info.showsign = 1; + break; + case '-': + /* Left-justify things. */ + spec->info.left = 1; + break; + case '#': + /* Use the "alternate form": + Hex has 0x or 0X, FP always has a decimal point. */ + spec->info.alt = 1; + break; + case '0': + /* Pad with 0s. */ + spec->info.pad = '0'; + break; + case '\'': + /* Show grouping in numbers if the locale information + indicates any. */ + spec->info.group = 1; + break; + } + if (spec->info.left) + spec->info.pad = ' '; + + /* Get the field width. */ + spec->width_arg = -1; + spec->info.width = 0; + if (*format == '*') + { + /* The field width is given in an argument. + A negative field width indicates left justification. */ + const char *begin = ++format; + + if (isdigit (*format)) + { + /* The width argument might be found in a positional parameter. */ + n = read_int (&format); + + if (n > 0 && *format == '$') + { + spec->width_arg = n - 1; + *max_ref_arg = MAX (*max_ref_arg, n); + ++format; /* Skip '$'. */ + } + } + + if (spec->width_arg < 0) + { + /* Not in a positional parameter. Consume one argument. */ + spec->width_arg = posn++; + ++nargs; + format = begin; /* Step back and reread. */ + } + } + else if (isdigit (*format)) + /* Constant width specification. */ + spec->info.width = read_int (&format); + + /* Get the precision. */ + spec->prec_arg = -1; + /* -1 means none given; 0 means explicit 0. */ + spec->info.prec = -1; + if (*format == '.') + { + ++format; + if (*format == '*') + { + /* The precision is given in an argument. */ + const char *begin = ++format; + + if (isdigit (*format)) + { + n = read_int (&format); + + if (n > 0 && *format == '$') + { + spec->prec_arg = n - 1; + *max_ref_arg = MAX (*max_ref_arg, n); + ++format; + } + } + + if (spec->prec_arg < 0) + { + /* Not in a positional parameter. */ + spec->prec_arg = posn++; + ++nargs; + format = begin; + } + } + else if (isdigit (*format)) + spec->info.prec = read_int (&format); + else + /* "%.?" is treated like "%.0?". */ + spec->info.prec = 0; + + /* If there was a precision specified, ignore the 0 flag and always + pad with spaces. */ + spec->info.pad = ' '; + } + + /* Check for type modifiers. */ +#define is_longlong is_long_double + spec->info.is_long_double = 0; + spec->info.is_short = 0; + spec->info.is_long = 0; + + while (*format == 'h' || *format == 'l' || *format == 'L' || + *format == 'Z' || *format == 'q') + switch (*format++) + { + case 'h': + /* int's are short int's. */ + spec->info.is_short = 1; + break; + case 'l': + if (spec->info.is_long) + /* A double `l' is equivalent to an `L'. */ + spec->info.is_longlong = 1; + else + /* int's are long int's. */ + spec->info.is_long = 1; + break; + case 'L': + /* double's are long double's, and int's are long long int's. */ + spec->info.is_long_double = 1; + break; + case 'Z': + /* int's are size_t's. */ + assert (sizeof(size_t) <= sizeof(unsigned long long int)); + spec->info.is_longlong = sizeof(size_t) > sizeof(unsigned long int); + spec->info.is_long = sizeof(size_t) > sizeof(unsigned int); + break; + case 'q': + /* 4.4 uses this for long long. */ + spec->info.is_longlong = 1; + break; + } + + /* Get the format specification. */ + spec->info.spec = *format++; + if (__printf_arginfo_table != NULL && + __printf_arginfo_table[spec->info.spec] != NULL) + /* We don't try to get the types for all arguments if the format + uses more than one. The normal case is covered though. */ + spec->ndata_args = (*__printf_arginfo_table[spec->info.spec]) + (&spec->info, 1, &spec->data_arg_type); + else + { + /* Find the data argument types of a built-in spec. */ + spec->ndata_args = 1; + + switch (spec->info.spec) + { + case 'i': + case 'd': + case 'u': + case 'o': + case 'X': + case 'x': + if (spec->info.is_longlong) + spec->data_arg_type = PA_INT|PA_FLAG_LONG_LONG; + else if (spec->info.is_long) + spec->data_arg_type = PA_INT|PA_FLAG_LONG; + else if (spec->info.is_short) + spec->data_arg_type = PA_INT|PA_FLAG_SHORT; + else + spec->data_arg_type = PA_INT; + break; + case 'e': + case 'E': + case 'f': + case 'g': + case 'G': + if (spec->info.is_long_double) + spec->data_arg_type = PA_DOUBLE|PA_FLAG_LONG_DOUBLE; + else + spec->data_arg_type = PA_DOUBLE; + break; + case 'c': + spec->data_arg_type = PA_CHAR; + break; + case 's': + spec->data_arg_type = PA_STRING; + break; + case 'p': + spec->data_arg_type = PA_POINTER; + break; + case 'n': + spec->data_arg_type = PA_INT|PA_FLAG_PTR; + break; + + case 'm': + default: + /* An unknown spec will consume no args. */ + spec->ndata_args = 0; + break; + } + + if (spec->data_arg == -1 && spec->ndata_args > 0) + { + /* There are args consumed, but no positional spec. + Use the next sequential arg position. */ + spec->data_arg = posn; + posn += spec->ndata_args; + nargs += spec->ndata_args; + } + } + + if (spec->info.spec == '\0') + /* Format ended before this spec was complete. */ + spec->end_of_fmt = spec->next_fmt = format - 1; + else + { + /* Find the next format spec. */ + spec->end_of_fmt = format; + spec->next_fmt = find_spec (format); + } + + return nargs; +} diff --git a/stdio/printf-prs.c b/stdio/printf-prs.c index 2f55dd3157..811a9cb7eb 100644 --- a/stdio/printf-prs.c +++ b/stdio/printf-prs.c @@ -16,196 +16,57 @@ License along with the GNU C Library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -#include #include #include -#include +#include #include -#include -#ifdef __GNUC__ -#define HAVE_LONGLONG -#endif +#include "printf-parse.h" -extern printf_arginfo_function *__printf_arginfo_table[]; size_t -DEFUN(parse_printf_format, (fmt, n, argtypes), - CONST char *fmt AND size_t n AND int *argtypes) +parse_printf_format (fmt, n, argtypes) + const char *fmt; + size_t n; + int *argtypes; { - register CONST char *f; - size_t need = 0; + size_t nargs; /* Number of arguments. */ + size_t max_ref_arg; /* Highest index used in a positional arg. */ + struct printf_spec spec; - for (f = strchr (fmt, '%'); f != NULL; f = strchr (f, '%')) - { - struct printf_info info; - printf_arginfo_function *arginfo; - - ++f; + nargs = 0; + max_ref_arg = 0; - info.space = info.showsign = info.left = info.alt = info.group = 0; - info.pad = ' '; - while (*f == ' ' || *f == '+' || *f == '-' || *f == '#' || *f == '0' || - *f == '\'') - switch (*f++) - { - case ' ': - info.space = 1; - break; - case '+': - info.showsign = 1; - break; - case '-': - info.left = 1; - break; - case '#': - info.alt = 1; - break; - case '\'': - info.group = 1; - break; - case '0': - info.pad = '0'; - break; - } - if (info.left) - info.pad = ' '; + /* Search for format specifications. */ + for (fmt = find_spec (fmt); *fmt != '\0'; fmt = spec.next_fmt) + { + /* Parse this spec. */ + nargs += parse_one_spec (fmt, nargs, &spec, &max_ref_arg); - /* Get the field width. */ - if (*f == '*') - { - if (++need < n) - *argtypes++ = PA_INT; - info.width = INT_MIN; - ++f; - } - else - { - info.width = 0; - while (isdigit(*f)) - { - info.width *= 10; - info.width += *f++ - '0'; - } - } + /* If the width is determined by an argument this is an int. */ + if (spec.width_arg != -1 && spec.width_arg < n) + argtypes[spec.width_arg] = PA_INT; - /* Get the precision. */ - /* -1 means none given; 0 means explicit 0. */ - info.prec = -1; - if (*f == '.') - { - ++f; - if (*f == '*') - { - /* The precision is given in an argument. */ - if (++need < n) - *argtypes++ = PA_INT; - info.prec = INT_MIN; - ++f; - } - else if (isdigit(*f)) - { - info.prec = 0; - while (*f != '\0' && isdigit(*f)) - { - info.prec *= 10; - info.prec += *f++ - '0'; - } - } - } + /* If the precision is determined by an argument this is an int. */ + if (spec.prec_arg != -1 && spec.prec_arg < n) + argtypes[spec.prec_arg] = PA_INT; - /* Check for type modifiers. */ - info.is_short = info.is_long = info.is_long_double = 0; - while (*f == 'h' || *f == 'l' || *f == 'L') - switch (*f++) + if (spec.data_arg < n) + switch (spec.ndata_args) { - case 'h': - /* int's are short int's. */ - info.is_short = 1; + case 0: /* No arguments. */ break; - case 'l': -#ifdef HAVE_LONGLONG - if (info.is_long) - /* A double `l' is equivalent to an `L'. */ - info.is_long_double = 1; - else -#endif - /* int's are long int's. */ - info.is_long = 1; + case 1: /* One argument; we already have the type. */ + argtypes[spec.data_arg] = spec.data_arg_type; break; - case 'L': - /* double's are long double's, and int's are long long int's. */ - info.is_long_double = 1; + default: + /* We have more than one argument for this format spec. We must + call the arginfo function again to determine all the types. */ + (void) (*__printf_arginfo_table[spec.info.spec]) + (&spec.info, n - spec.data_arg, &argtypes[spec.data_arg]); break; } - - if (*f == '\0') - return need; - - info.spec = *f++; - - arginfo = __printf_arginfo_table[info.spec]; - if (arginfo != NULL) - { - size_t nargs - = (*arginfo) (&info, need > n ? 0 : n - need, argtypes); - need += nargs; - argtypes += nargs; - } - else - { - int type; - switch (info.spec) - { - case 'i': - case 'd': - case 'u': - case 'o': - case 'X': - case 'x': - type = PA_INT; - break; - - case 'e': - case 'E': - case 'f': - case 'g': - case 'G': - type = PA_DOUBLE; - break; - - case 'c': - type = PA_CHAR; - break; - - case 's': - type = PA_STRING; - break; - - case 'p': - type = PA_POINTER; - break; - - case 'n': - type = PA_INT | PA_FLAG_PTR; - break; - - default: - /* No arg for an unknown spec. */ - continue; - } - - if (info.is_long_double) - type |= PA_FLAG_LONG_DOUBLE; - if (info.is_long) - type |= PA_FLAG_LONG; - if (info.is_short) - type |= PA_FLAG_SHORT; - - if (++need < n) - *argtypes++ = type; - } } - return need; + return MAX (nargs, max_ref_arg); } diff --git a/stdio/vfprintf.c b/stdio/vfprintf.c index c2462178ed..056ea32bee 100644 --- a/stdio/vfprintf.c +++ b/stdio/vfprintf.c @@ -16,20 +16,23 @@ License along with the GNU C Library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -#include -#include "../locale/localeinfo.h" #include #include #include #include #include +#include #include #include #include #include -#include #include #include "_itoa.h" +#include "../locale/localeinfo.h" + +/* Include the shared code for parsing the format string. */ +#include "printf-parse.h" + /* This function from the GNU C library is also used in libio. To compile for use in libio, compile with -DUSE_IN_LIBIO. */ @@ -38,20 +41,21 @@ Cambridge, MA 02139, USA. */ /* This code is for use in libio. */ #include #define PUT(f, s, n) _IO_sputn (f, s, n) -#define PAD(padchar) \ - (width > 0 ? width += _IO_padn (s, padchar, width) : 0) -#define PUTC(c, f) _IO_putc(c, f) +#define PAD(padchar) \ + if (specs[cnt].info.width > 0) \ + done += _IO_padn (s, padchar, specs[cnt].info.width) +#define PUTC(c, f) _IO_putc (c, f) #define vfprintf _IO_vfprintf #define size_t _IO_size_t #define FILE _IO_FILE #define va_list _IO_va_list #undef BUFSIZ #define BUFSIZ _IO_BUFSIZ -#define ARGCHECK(s, format) \ +#define ARGCHECK(s, format) \ do \ { \ /* Check file argument for consistence. */ \ - CHECK_FILE(s, -1); \ + CHECK_FILE (s, -1); \ if (s->_flags & _IO_NO_WRITES || format == NULL) \ { \ MAYBE_SET_EINVAL; \ @@ -64,8 +68,11 @@ Cambridge, MA 02139, USA. */ #include #define PUTC(c, f) putc (c, f) #define PUT(f, s, n) fwrite (s, 1, n, f) -ssize_t __printf_pad __P ((FILE *, char pad, int n)); -#define PAD(padchar) __printf_pad (s, padchar, width) +ssize_t __printf_pad __P ((FILE *, char pad, size_t n)); +#define PAD(padchar) \ + if (specs[cnt].info.width > 0) \ + { if (__printf_pad (s, padchar, specs[cnt].info.width) == -1) \ + return -1; else done += specs[cnt].info.width; } #define ARGCHECK(s, format) \ do \ { \ @@ -88,14 +95,13 @@ ssize_t __printf_pad __P ((FILE *, char pad, int n)); #define outchar(x) \ do \ { \ - register CONST int outc = (x); \ - if (putc(outc, s) == EOF) \ + register const int outc = (x); \ + if (putc (outc, s) == EOF) \ return -1; \ else \ ++done; \ } while (0) -/* Advances STRING after writing LEN chars of it. */ #define outstring(string, len) \ do \ { \ @@ -104,580 +110,525 @@ ssize_t __printf_pad __P ((FILE *, char pad, int n)); if (PUT (s, string, len) != len) \ return -1; \ done += len; \ - string += len; \ } \ else \ - while (len-- > 0) \ - outchar (*string++); \ + { \ + register const char *cp = string; \ + register int l = len; \ + while (l-- > 0) \ + outchar (*cp++); \ + } \ } while (0) /* Helper function to provide temporary buffering for unbuffered streams. */ static int buffered_vfprintf __P ((FILE *stream, const char *fmt, va_list)); -/* Cast the next arg, of type ARGTYPE, into CASTTYPE, and put it in VAR. */ -#define castarg(var, argtype, casttype) \ - var = (casttype) va_arg(args, argtype) -/* Get the next arg, of type TYPE, and put it in VAR. */ -#define nextarg(var, type) castarg(var, type, type) - static printf_function printf_unknown; extern printf_function **__printf_function_table; -#ifdef __GNUC__ -#define HAVE_LONGLONG -#define LONGLONG long long -#else -#define LONGLONG long -#endif - static char *group_number __P ((char *, char *, const char *, wchar_t)); + int -DEFUN(vfprintf, (s, format, args), - register FILE *s AND CONST char *format AND va_list args) +vfprintf (s, format, ap) + register FILE *s; + const char *format; + va_list ap; { /* The character used as thousands separator. */ wchar_t thousands_sep; /* The string describing the size of groups of digits. */ - const char *grouping; + const char *grouping; + + /* Array with information about the needed arguments. This has to be + dynamically extendable. */ + size_t nspecs; + size_t nspecs_max; + struct printf_spec *specs; + + /* The number of arguments the format string requests. This will + determine the size of the array needed to store the argument + attributes. */ + size_t nargs; + int *args_type; + union printf_arg *args_value; + + /* Positional parameters refer to arguments directly. This could also + determine the maximum number of arguments. Track the maximum number. */ + size_t max_ref_arg; - /* Pointer into the format string. */ - register CONST char *f; + /* End of leading constant string. */ + const char *lead_str_end; /* Number of characters written. */ register size_t done = 0; + /* Running pointer through format string. */ + const char *f; + + /* Just a counter. */ + int cnt; + ARGCHECK (s, format); if (UNBUFFERED_P (s)) /* Use a helper function which will allocate a local temporary buffer for the stream and then call us again. */ - return buffered_vfprintf (s, format, args); + return buffered_vfprintf (s, format, ap); /* Reset multibyte characters to their initial state. */ (void) mblen ((char *) NULL, 0); - /* Figure out the thousands seperator character. */ + /* Figure out the thousands separator character. */ if (mbtowc (&thousands_sep, _NL_CURRENT (LC_NUMERIC, THOUSANDS_SEP), - strlen (_NL_CURRENT (LC_NUMERIC, THOUSANDS_SEP))) <= 0) + strlen (_NL_CURRENT (LC_NUMERIC, THOUSANDS_SEP))) <= 0) thousands_sep = (wchar_t) *_NL_CURRENT (LC_NUMERIC, THOUSANDS_SEP); grouping = _NL_CURRENT (LC_NUMERIC, GROUPING); - if (*grouping == '\0' || thousands_sep == L'\0') + if (*grouping == '\0' || *grouping == CHAR_MAX || thousands_sep == L'\0') grouping = NULL; - f = format; - while (*f != '\0') + nspecs_max = 32; /* A more or less arbitrary start value. */ + specs = alloca (nspecs_max * sizeof (struct printf_spec)); + nspecs = 0; + nargs = 0; + max_ref_arg = 0; + + /* Find the first format specifier. */ + lead_str_end = find_spec (format); + + for (f = lead_str_end; *f != '\0'; f = specs[nspecs++].next_fmt) { - /* Type modifiers. */ - char is_short, is_long, is_long_double; -#ifdef HAVE_LONGLONG - /* We use the `L' modifier for `long long int'. */ -#define is_longlong is_long_double -#else -#define is_longlong 0 -#endif - /* Format spec modifiers. */ - char space, showsign, left, alt, group; - - /* Padding character: ' ' or '0'. */ - char pad; - /* Width of a field. */ - register int width; - /* Precision of a field. */ - int prec; - - /* Decimal integer is negative. */ - char is_neg; - - /* Current character of the format. */ - char fc; - - /* Base of a number to be written. */ - int base; - /* Integral values to be written. */ - unsigned LONGLONG int num; - LONGLONG int signed_num; - - /* String to be written. */ - CONST char *str; - char errorbuf[1024]; /* Buffer sometimes used by %m. */ - - /* Auxiliary function to do output. */ - printf_function *function; - - if (!isascii(*f)) + if (nspecs >= nspecs_max) { - /* Non-ASCII, may be a multibyte. */ - int len = mblen (f, strlen (f)); - if (len > 0) + /* Extend the array of format specifiers. */ + struct printf_spec *old = specs; + + nspecs_max *= 2; + specs = alloca (nspecs_max * sizeof (struct printf_spec)); + if (specs == &old[nspecs]) + /* Stack grows up, OLD was the last thing allocated; extend it. */ + nspecs_max += nspecs_max / 2; + else { - outstring (f, len); - continue; + /* Copy the old array's elements to the new space. */ + memcpy (specs, old, nspecs * sizeof (struct printf_spec)); + if (old == &specs[nspecs]) + /* Stack grows down, OLD was just below the new SPECS. + We can use that space when the new space runs out. */ + nspecs_max += nspecs_max / 2; } } - if (*f != '%') - { - /* This isn't a format spec, so write everything out until the - next one. To properly handle multibyte characters, we cannot - just search for a '%'. Since multibyte characters are hairy - (and dealt with above), if we hit any byte above 127 (only - those can start a multibyte character) we just punt back to - that code. */ - do - outchar (*f++); - while (*f != '\0' && *f != '%' && isascii (*f)); - continue; - } + /* Parse the format specifier. */ + nargs += parse_one_spec (f, nargs, &specs[nspecs], &max_ref_arg); + } + + /* Determine the number of arguments the format string consumes. */ + nargs = MAX (nargs, max_ref_arg); + + /* Allocate memory for the argument descriptions. */ + args_type = alloca (nargs * sizeof (int)); + args_value = alloca (nargs * sizeof (union printf_arg)); + + /* XXX Could do sanity check here: + Initialize args_type elts to zero. + If any is still zero after this loop, format is invalid. */ + + /* Fill in the types of all the arguments. */ + for (cnt = 0; cnt < nspecs; ++cnt) + { + /* If the width is determined by an argument this is an int. */ + if (specs[cnt].width_arg != -1) + args_type[specs[cnt].width_arg] = PA_INT; - ++f; + /* If the precision is determined by an argument this is an int. */ + if (specs[cnt].prec_arg != -1) + args_type[specs[cnt].prec_arg] = PA_INT; - /* Check for "%%". Note that although the ANSI standard lists - '%' as a conversion specifier, it says "The complete format - specification shall be `%%'," so we can avoid all the width - and precision processing. */ - if (*f == '%') + switch (specs[cnt].ndata_args) { - ++f; - outchar('%'); - continue; + case 0: /* No arguments. */ + break; + case 1: /* One argument; we already have the type. */ + args_type[specs[cnt].data_arg] = specs[cnt].data_arg_type; + break; + default: + /* We have more than one argument for this format spec. We must + call the arginfo function again to determine all the types. */ + (void) (*__printf_arginfo_table[specs[cnt].info.spec]) + (&specs[cnt].info, + specs[cnt].ndata_args, &args_type[specs[cnt].data_arg]); + break; } + } - /* Check for spec modifiers. */ - space = showsign = left = alt = group = 0; - pad = ' '; - while (*f == ' ' || *f == '+' || *f == '-' || *f == '#' || *f == '0' || - *f == '\'') - switch (*f++) - { - case ' ': - /* Output a space in place of a sign, when there is no sign. */ - space = 1; - break; - case '+': - /* Always output + or - for numbers. */ - showsign = 1; - break; - case '-': - /* Left-justify things. */ - left = 1; - break; - case '#': - /* Use the "alternate form": - Hex has 0x or 0X, FP always has a decimal point. */ - alt = 1; - break; - case '0': - /* Pad with 0s. */ - pad = '0'; - break; - case '\'': - /* Show grouping in numbers if the locale information - indicates any. */ - group = 1; - break; - } - if (left) - pad = ' '; - - /* Get the field width. */ - width = 0; - if (*f == '*') + /* Now we know all the types and the order. Fill in the argument values. */ + for (cnt = 0; cnt < nargs; ++cnt) + switch (args_type[cnt]) + { +#define T(tag, mem, type) \ + case tag: \ + args_value[cnt].mem = va_arg (ap, type); \ + break + + T (PA_CHAR, pa_char, int); /* Promoted. */ + T (PA_INT|PA_FLAG_SHORT, pa_short_int, int); /* Promoted. */ + T (PA_INT, pa_int, int); + T (PA_INT|PA_FLAG_LONG, pa_long_int, long int); + T (PA_INT|PA_FLAG_LONG_LONG, pa_long_long_int, long long int); + T (PA_FLOAT, pa_float, double); /* Promoted. */ + T (PA_DOUBLE, pa_double, double); + T (PA_DOUBLE|PA_FLAG_LONG_DOUBLE, pa_long_double, long double); + T (PA_STRING, pa_string, const char *); + T (PA_POINTER, pa_pointer, void *); +#undef T + default: + if ((args_type[cnt] & PA_FLAG_PTR) != 0) + args_value[cnt].pa_pointer = va_arg (ap, void *); + break; + } + + /* Write the literal text before the first format. */ + outstring (format, lead_str_end - format); + + /* Now walk through all format specifiers and process them. */ + for (cnt = 0; cnt < nspecs; ++cnt) + { + printf_function *function; /* Auxiliary function to do output. */ + int is_neg; /* Decimal integer is negative. */ + int base; /* Base of a number to be written. */ + unsigned long long int num; /* Integral number to be written. */ + const char *str; /* String to be written. */ + char errorbuf[1024]; /* Buffer sometimes used by %m. */ + + if (specs[cnt].width_arg != -1) { - /* The field width is given in an argument. - A negative field width indicates left justification. */ - nextarg(width, int); - if (width < 0) + /* Extract the field width from an argument. */ + specs[cnt].info.width = args_value[specs[cnt].width_arg].pa_int; + + if (specs[cnt].info.width < 0) + /* If the width value is negative left justification is selected + and the value is taken as being positive. */ { - width = - width; - left = 1; + specs[cnt].info.width = -specs[cnt].info.width; + specs[cnt].info.left = 1; } - ++f; } - else - while (isdigit (*f)) - { - width *= 10; - width += *f++ - '0'; - } - /* Get the precision. */ - /* -1 means none given; 0 means explicit 0. */ - prec = -1; - if (*f == '.') + if (specs[cnt].prec_arg != -1) { - ++f; - if (*f == '*') - { - /* The precision is given in an argument. */ - nextarg(prec, int); - /* Avoid idiocy. */ - if (prec < 0) - prec = -1; - ++f; - } - else if (isdigit (*f)) - { - prec = *f++ - '0'; - while (*f != '\0' && isdigit (*f)) - { - prec *= 10; - prec += *f++ - '0'; - } - } - else - /* "%.?" is treated like "%.0?". */ - prec = 0; + /* Extract the precision from an argument. */ + specs[cnt].info.prec = args_value[specs[cnt].prec_arg].pa_int; + + if (specs[cnt].info.prec < 0) + /* If the precision is negative the precision is omitted. */ + specs[cnt].info.prec = -1; } - /* If there was a precision specified, ignore the 0 flag and always - pad with spaces. */ - if (prec != -1) - pad = ' '; + /* Check for a user-defined handler for this spec. */ + function = (__printf_function_table == NULL ? NULL : + __printf_function_table[specs[cnt].info.spec]); - /* Check for type modifiers. */ - is_short = is_long = is_long_double = 0; - while (*f == 'h' || *f == 'l' || *f == 'L' || *f == 'q' || *f == 'Z') - switch (*f++) - { - case 'h': - /* int's are short int's. */ - is_short = 1; - break; - case 'l': -#ifdef HAVE_LONGLONG - if (is_long) - /* A double `l' is equivalent to an `L'. */ - is_longlong = 1; - else -#endif - /* int's are long int's. */ - is_long = 1; - break; - case 'L': - /* double's are long double's, and int's are long long int's. */ - is_long_double = 1; - break; + if (function != NULL) + use_function: /* Built-in formats with helpers use this. */ + { + int function_done; + unsigned int i; + const void *ptr[specs[cnt].ndata_args]; - case 'Z': - /* int's are size_t's. */ -#ifdef HAVE_LONGLONG - assert (sizeof(size_t) <= sizeof(unsigned long long int)); - is_longlong = sizeof(size_t) > sizeof(unsigned long int); -#endif - is_long = sizeof(size_t) > sizeof(unsigned int); - break; + /* Fill in an array of pointers to the argument values. */ + for (i = 0; i < specs[cnt].ndata_args; ++i) + ptr[i] = &args_value[specs[cnt].data_arg + i]; - case 'q': - /* 4.4 uses this for long long. */ -#ifdef HAVE_LONGLONG - is_longlong = 1; -#else - is_long = 1; -#endif - break; - } + /* Call the function. */ + function_done = (*function) (s, &specs[cnt].info, ptr); - /* Format specification. */ - fc = *f++; - function = (__printf_function_table == NULL ? NULL : - __printf_function_table[fc]); - if (function == NULL) - switch (fc) + /* If an error occured don't do any further work. */ + if (function_done < 0) + return -1; + + done += function_done; + } + else + switch (specs[cnt].info.spec) { + case '%': + /* Write a literal "%". */ + outchar ('%'); + break; case 'i': case 'd': - /* Decimal integer. */ - base = 10; - if (is_longlong) - nextarg(signed_num, LONGLONG int); - else if (is_long) - nextarg(signed_num, long int); - else if (!is_short) - castarg(signed_num, int, long int); - else - castarg(signed_num, int, short int); - - is_neg = signed_num < 0; - num = is_neg ? (- signed_num) : signed_num; - goto number; + { + long long int signed_num; + + /* Decimal integer. */ + base = 10; + if (specs[cnt].info.is_longlong) + signed_num = args_value[specs[cnt].data_arg].pa_long_long_int; + else if (specs[cnt].info.is_long) + signed_num = args_value[specs[cnt].data_arg].pa_long_int; + else if (!specs[cnt].info.is_short) + signed_num = args_value[specs[cnt].data_arg].pa_int; + else + signed_num = args_value[specs[cnt].data_arg].pa_short_int; + + is_neg = signed_num < 0; + num = is_neg ? (- signed_num) : signed_num; + goto number; + } case 'u': /* Decimal unsigned integer. */ - base = 10; - goto unsigned_number; + base = 10; + goto unsigned_number; case 'o': - /* Octal unsigned integer. */ - base = 8; - goto unsigned_number; + /* Octal unsigned integer. */ + base = 8; + goto unsigned_number; - case 'X': - /* Hexadecimal unsigned integer. */ - case 'x': - /* Hex with lower-case digits. */ - - base = 16; + case 'X': + /* Hexadecimal unsigned integer. */ + case 'x': + /* Hex with lower-case digits. */ + base = 16; unsigned_number: - /* Unsigned number of base BASE. */ - - if (is_longlong) - castarg(num, LONGLONG int, unsigned LONGLONG int); - else if (is_long) - castarg(num, long int, unsigned long int); - else if (!is_short) - castarg(num, int, unsigned int); - else - castarg(num, int, unsigned short int); - - /* ANSI only specifies the `+' and - ` ' flags for signed conversions. */ - is_neg = showsign = space = 0; + /* Unsigned number of base BASE. */ + + if (specs[cnt].info.is_longlong) + num = args_value[specs[cnt].data_arg].pa_u_long_long_int; + else if (specs[cnt].info.is_long) + num = args_value[specs[cnt].data_arg].pa_u_long_int; + else if (!specs[cnt].info.is_short) + num = args_value[specs[cnt].data_arg].pa_u_int; + else + num = args_value[specs[cnt].data_arg].pa_u_short_int; + + /* ANSI only specifies the `+' and + ` ' flags for signed conversions. */ + is_neg = 0; + specs[cnt].info.showsign = 0; + specs[cnt].info.space = 0; number: /* Number of base BASE. */ - { - char work[BUFSIZ]; - char *CONST workend = &work[sizeof(work) - 1]; - register char *w; - - /* Supply a default precision if none was given. */ - if (prec == -1) - prec = 1; - - /* Put the number in WORK. */ - w = _itoa (num, workend + 1, base, fc == 'X') - 1; - if (group && grouping) - w = group_number (w, workend, grouping, thousands_sep); - width -= workend - w; - prec -= workend - w; - - if (alt && base == 8 && prec <= 0) - { - *w-- = '0'; - --width; - } - - if (prec > 0) - { - width -= prec; - while (prec-- > 0) - *w-- = '0'; - } - - if (alt && base == 16) - width -= 2; - - if (is_neg || showsign || space) - --width; - - if (!left && pad == ' ') - PAD (' '); - - if (is_neg) - outchar('-'); - else if (showsign) - outchar('+'); - else if (space) - outchar(' '); - - if (alt && base == 16) - { - outchar ('0'); - outchar (fc); - } - - if (!left && pad == '0') - PAD ('0'); - - /* Write the number. */ - while (++w <= workend) - outchar(*w); - - if (left) - PAD (' '); - } - break; - - case 'e': - case 'E': - case 'f': - case 'g': - case 'G': - { - /* Floating-point number. */ - extern printf_function __printf_fp; - function = __printf_fp; - goto use_function; - } - - case 'c': - /* Character. */ - nextarg(num, int); - if (!left) - { - --width; - PAD (' '); - } - outchar ((unsigned char) num); - if (left) - PAD (' '); - break; - - case 's': - { - static CONST char null[] = "(null)"; - size_t len; - - nextarg(str, CONST char *); + { + char work[BUFSIZ]; + char *const workend = &work[sizeof(work) - 1]; + register char *w; + + /* Supply a default precision if none was given. */ + if (specs[cnt].info.prec == -1) + specs[cnt].info.prec = 1; + + /* Put the number in WORK. */ + w = _itoa (num, workend + 1, base, specs[cnt].info.spec == 'X'); + w -= 1; + if (specs[cnt].info.group && grouping) + w = group_number (w, workend, grouping, thousands_sep); + specs[cnt].info.width -= workend - w; + specs[cnt].info.prec -= workend - w; + + if (num != 0 && specs[cnt].info.alt && base == 8 + && specs[cnt].info.prec <= 0) + { + /* Add octal marker. */ + *w-- = '0'; + --specs[cnt].info.width; + } + + if (specs[cnt].info.prec > 0) + { + /* Add zeros to the precision. */ + specs[cnt].info.width -= specs[cnt].info.prec; + while (specs[cnt].info.prec-- > 0) + *w-- = '0'; + } + + if (num != 0 && specs[cnt].info.alt && base == 16) + /* Account for 0X hex marker. */ + specs[cnt].info.width -= 2; + + if (is_neg || specs[cnt].info.showsign || specs[cnt].info.space) + --specs[cnt].info.width; + + if (!specs[cnt].info.left && specs[cnt].info.pad == ' ') + PAD (' '); + + if (is_neg) + outchar ('-'); + else if (specs[cnt].info.showsign) + outchar ('+'); + else if (specs[cnt].info.space) + outchar (' '); + + if (num != 0 && specs[cnt].info.alt && base == 16) + { + outchar ('0'); + outchar (specs[cnt].info.spec); + } + + if (!specs[cnt].info.left && specs[cnt].info.pad == '0') + PAD ('0'); + + /* Write the number. */ + while (++w <= workend) + outchar (*w); + + if (specs[cnt].info.left) + PAD (' '); + } + break; + + case 'e': + case 'E': + case 'f': + case 'g': + case 'G': + { + /* Floating-point number. This is handled by printf_fp.c. */ + extern printf_function __printf_fp; + function = __printf_fp; + goto use_function; + } + + case 'c': + /* Character. */ + if (!specs[cnt].info.left) + { + --specs[cnt].info.width; + PAD (' '); + } + outchar ((unsigned char) args_value[specs[cnt].data_arg].pa_char); + if (specs[cnt].info.left) + PAD (' '); + break; + + case 's': + { + static const char null[] = "(null)"; + size_t len; + + str = args_value[specs[cnt].data_arg].pa_string; string: - if (str == NULL) - /* Write "(null)" if there's space. */ - if (prec == -1 || prec >= (int) sizeof(null) - 1) - { - str = null; - len = sizeof(null) - 1; - } - else - { - str = ""; - len = 0; - } - else - len = strlen(str); - - if (prec != -1 && (size_t) prec < len) - len = prec; - width -= len; - - if (!left) - PAD (' '); - outstring (str, len); - if (left) - PAD (' '); - } - break; - - case 'p': - /* Generic pointer. */ - { - CONST PTR ptr; - nextarg(ptr, CONST PTR); - if (ptr != NULL) + if (str == NULL) { - /* If the pointer is not NULL, write it as a %#x spec. */ - base = 16; - fc = 'x'; - alt = 1; - num = (unsigned LONGLONG int) (unsigned long int) ptr; - is_neg = 0; - group = 0; - goto number; + /* Write "(null)" if there's space. */ + if (specs[cnt].info.prec == -1 + || specs[cnt].info.prec >= (int) sizeof (null) - 1) + { + str = null; + len = sizeof (null) - 1; + } + else + { + str = ""; + len = 0; + } } - else - { - /* Write "(nil)" for a nil pointer. */ - static CONST char nil[] = "(nil)"; - register CONST char *p; - - width -= sizeof (nil) - 1; - if (!left) - PAD (' '); - for (p = nil; *p != '\0'; ++p) - outchar (*p); - if (left) - PAD (' '); - } - } - break; - - case 'n': - /* Answer the count of characters written. */ - if (is_longlong) - { - LONGLONG int *p; - nextarg(p, LONGLONG int *); - *p = done; - } - else if (is_long) - { - long int *p; - nextarg(p, long int *); - *p = done; - } - else if (!is_short) - { - int *p; - nextarg(p, int *); - *p = done; - } - else - { - short int *p; - nextarg(p, short int *); - *p = done; - } - break; - - case 'm': - { - extern char *_strerror_internal __P ((int, char buf[1024])); - str = _strerror_internal (errno, errorbuf); - goto string; - } - - default: - /* Unrecognized format specifier. */ - function = printf_unknown; - goto use_function; + else + len = strlen (str); + + if (specs[cnt].info.prec != -1 + && (size_t) specs[cnt].info.prec < len) + /* Limit the length to the precision. */ + len = specs[cnt].info.prec; + specs[cnt].info.width -= len; + + if (!specs[cnt].info.left) + PAD (' '); + outstring (str, len); + if (specs[cnt].info.left) + PAD (' '); + } + break; + + case 'p': + /* Generic pointer. */ + { + const void *ptr; + ptr = args_value[specs[cnt].data_arg].pa_pointer; + if (ptr != NULL) + { + /* If the pointer is not NULL, write it as a %#x spec. */ + base = 16; + num = (unsigned long long int) (unsigned long int) ptr; + is_neg = 0; + specs[cnt].info.alt = 1; + specs[cnt].info.spec = 'x'; + specs[cnt].info.group = 0; + goto number; + } + else + { + /* Write "(nil)" for a nil pointer. */ + str = "(nil)"; + /* Make sure the full string "(nil)" is printed. */ + if (specs[cnt].info.prec < 5) + specs[cnt].info.prec = 5; + goto string; + } + } + break; + + case 'n': + /* Answer the count of characters written. */ + if (specs[cnt].info.is_longlong) + *(long long int *) + args_value[specs[cnt].data_arg].pa_pointer = done; + else if (specs[cnt].info.is_long) + *(long int *) + args_value[specs[cnt].data_arg].pa_pointer = done; + else if (!specs[cnt].info.is_short) + *(int *) + args_value[specs[cnt].data_arg].pa_pointer = done; + else + *(short int *) + args_value[specs[cnt].data_arg].pa_pointer = done; + break; + + case 'm': + { + extern char *_strerror_internal __P ((int, char buf[1024])); + str = _strerror_internal (errno, errorbuf); + goto string; + } + + default: + /* Unrecognized format specifier. */ + function = printf_unknown; + goto use_function; } - else - use_function: - { - int function_done; - struct printf_info info; - - info.prec = prec; - info.width = width; - info.spec = fc; - info.is_long_double = is_long_double; - info.is_short = is_short; - info.is_long = is_long; - info.alt = alt; - info.space = space; - info.left = left; - info.showsign = showsign; - info.group = group; - info.pad = pad; - - function_done = (*function) (s, &info, &args); - if (function_done < 0) - return -1; - done += function_done; - } + /* Write the following constant string. */ + outstring (specs[cnt].end_of_fmt, + specs[cnt].next_fmt - specs[cnt].end_of_fmt); } return done; } +/* Handle an unknown format specifier. This prints out a canonicalized + representation of the format spec itself. */ + static int -DEFUN(printf_unknown, (s, info, arg), - FILE *s AND CONST struct printf_info *info AND va_list *arg) +printf_unknown (s, info, args) + FILE *s; + const struct printf_info *info; + const void **const args; { int done = 0; char work[BUFSIZ]; - char *CONST workend = &work[sizeof(work) - 1]; + char *const workend = &work[sizeof(work) - 1]; register char *w; - register int prec = info->prec, width = info->width; - outchar('%'); + outchar ('%'); if (info->alt) outchar ('#'); @@ -692,29 +643,23 @@ DEFUN(printf_unknown, (s, info, arg), if (info->pad == '0') outchar ('0'); - w = workend; - while (width > 0) + if (info->width != 0) { - *w-- = '0' + (width % 10); - width /= 10; + w = _itoa (info->width, workend + 1, 10, 0); + while (++w <= workend) + outchar (*w); } - while (++w <= workend) - outchar(*w); if (info->prec != -1) { - outchar('.'); - w = workend; - while (prec > 0) - { - *w-- = '0' + (prec % 10); - prec /= 10; - } + outchar ('.'); + w = _itoa (info->prec, workend + 1, 10, 0); while (++w <= workend) - outchar(*w); + outchar (*w); } - outchar(info->spec); + if (info->spec != '\0') + outchar (info->spec); return done; } @@ -768,7 +713,6 @@ group_number (char *w, char *workend, const char *grouping, } } } - return w; } @@ -781,7 +725,9 @@ struct helper_file }; static int -DEFUN(_IO_helper_overflow, (s, c), _IO_FILE *s AND int c) +_IO_helper_overflow (s, c) + _IO_FILE *s; + int c; { _IO_FILE *target = ((struct helper_file*) s)->_put_stream; int used = s->_IO_write_ptr - s->_IO_write_base; @@ -815,8 +761,10 @@ static const struct _IO_jump_t _IO_helper_jumps = }; static int -DEFUN(buffered_vfprintf, (s, format, args), - register _IO_FILE *s AND char CONST *format AND _IO_va_list args) +buffered_vfprintf (s, format, args) + register _IO_FILE *s; + char const *format; + _IO_va_list args; { char buf[_IO_BUFSIZ]; struct helper_file helper; @@ -847,8 +795,10 @@ DEFUN(buffered_vfprintf, (s, format, args), #else /* !USE_IN_LIBIO */ static int -DEFUN(buffered_vfprintf, (s, format, args), - register FILE *s AND char CONST *format AND va_list args) +buffered_vfprintf (s, format, args) + register FILE *s; + char const *format; + va_list args; { char buf[BUFSIZ]; int result; @@ -882,27 +832,21 @@ ssize_t __printf_pad (s, pad, count) FILE *s; char pad; - int count; + size_t count; { - CONST char *padptr; - register int i; - size_t written = 0, w; + const char *padptr; + register size_t i; padptr = pad == ' ' ? blanks : zeroes; for (i = count; i >= PADSIZE; i -= PADSIZE) - { - w = PUT(s, padptr, PADSIZE); - written += w; - if (w != PADSIZE) - return written; - } + if (PUT (s, padptr, PADSIZE) != PADSIZE) + return -1; if (i > 0) - { - w = PUT(s, padptr, i); - written += w; - } - return written; + if (PUT (s, padptr, i) != i) + return -1; + + return count; } #undef PADSIZE #endif /* USE_IN_LIBIO */ -- cgit 1.4.1