/* Copyright (C) 1991, 1992, 1993, 1994 Free Software Foundation, Inc. This file is part of the 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 #include #include #include #include #include #include #include #include #include #include "_itoa.h" /* If it's an unbuffered stream that we provided temporary buffering for, remove that buffering. */ #define RETURN(x) \ do \ { \ done = (x); \ goto do_return; \ } while (0) #define outchar(x) \ do \ { \ register CONST int outc = (x); \ if (putc(outc, s) == EOF) \ RETURN(-1); \ else \ ++done; \ } while (0) /* 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 int DEFUN(vfprintf, (s, format, args), register FILE *s AND CONST char *format AND va_list args) { /* Pointer into the format string. */ register CONST char *f; /* Number of characters written. */ register size_t done = 0; /* Nonzero we're providing buffering. */ char our_buffer; /* Temporary buffer for unbuffered streams. */ char temporary_buffer[BUFSIZ]; if (!__validfp(s) || !s->__mode.__write || format == NULL) { errno = EINVAL; return -1; } if (!s->__seen) { if (__flshfp(s, EOF) == EOF) return EOF; } our_buffer = s->__buffer == NULL; if (our_buffer) { /* If it's an unbuffered stream, buffer it at least inside this function call. */ s->__bufp = s->__buffer = temporary_buffer; s->__bufsize = sizeof(temporary_buffer); s->__put_limit = s->__buffer + s->__bufsize; s->__get_limit = s->__buffer; } /* Reset multibyte characters to their initial state. */ (void) mblen((char *) NULL, 0); f = format; while (*f != '\0') { /* 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; /* 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 unknown_error[256]; /* Buffer sometimes used by %m. */ /* Auxiliary function to do output. */ printf_function *function; if (!isascii(*f)) { /* Non-ASCII, may be a multibyte. */ int len = mblen(f, strlen(f)); if (len > 0) { while (len-- > 0) outchar(*f++); continue; } } if (*f != '%') { /* This isn't a format spec, so write everything out until the next one. */ CONST char *next = strchr(f + 1, '%'); if (next == NULL) next = strchr(f + 1, '\0'); if (next - f > 20) { size_t written = fwrite((PTR) f, 1, next - f, s); done += written; if (written != next - f) break; f += written; } else while (f < next) outchar(*f++); continue; } ++f; /* 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 == '%') { ++f; outchar('%'); continue; } /* Check for spec modifiers. */ space = showsign = left = alt = 0; pad = ' '; while (*f == ' ' || *f == '+' || *f == '-' || *f == '#' || *f == '0') 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; } if (left) pad = ' '; /* Get the field width. */ width = 0; if (*f == '*') { /* The field width is given in an argument. A negative field width indicates left justification. */ nextarg(width, int); if (width < 0) { width = - width; 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 == '.') { ++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 = 0; while (*f != '\0' && isdigit(*f)) { prec *= 10; prec += *f++ - '0'; } } } /* Check for type modifiers. */ is_short = is_long = is_long_double = 0; while (*f == 'h' || *f == 'l' || *f == 'L' || *f == 'q') 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; 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; case 'q': /* 4.4 uses this for long long. */ #ifdef HAVE_LONGLONG is_longlong = 1; #else is_long = 1; #endif break; } /* Format specification. */ fc = *f++; function = (__printf_function_table == NULL ? NULL : __printf_function_table[fc]); if (function == NULL) switch (fc) { 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; case 'u': /* Decimal unsigned integer. */ base = 10; goto unsigned_number; case 'o': /* Octal unsigned integer. */ base = 8; goto unsigned_number; 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; 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; 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 == ' ') while (width-- > 0) outchar(' '); if (is_neg) outchar('-'); else if (showsign) outchar('+'); else if (space) outchar(' '); if (alt && base == 16) { outchar ('0'); outchar (fc); } if (!left && pad == '0') while (width-- > 0) outchar('0'); /* Write the number. */ while (++w <= workend) outchar(*w); if (left) while (width-- > 0) outchar(' '); } 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) while (--width > 0) outchar(' '); outchar((unsigned char) num); if (left) while (--width > 0) outchar(' '); break; case 's': { static CONST char null[] = "(null)"; size_t len; nextarg(str, CONST char *); 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) while (width-- > 0) outchar(' '); if (len < 20) while (len-- > 0) outchar(*str++); else if (fwrite(str, 1, len, s) != len) RETURN(-1); else done += len; if (left) while (width-- > 0) outchar(' '); } break; case 'p': /* Generic pointer. */ { CONST PTR ptr; nextarg(ptr, CONST PTR); if (ptr != 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; goto number; } else { /* Write "(nil)" for a nil pointer. */ static CONST char nil[] = "(nil)"; register CONST char *p; width -= sizeof (nil) - 1; if (!left) while (width-- > 0) outchar (' '); for (p = nil; *p != '\0'; ++p) outchar (*p); if (left) while (width-- > 0) outchar (' '); } } 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': #ifndef HAVE_GNU_LD #define _sys_errlist sys_errlist #define _sys_nerr sys_nerr #endif if (errno < 0 || errno > _sys_nerr) { sprintf (unknown_error, "Unknown error %d", errno); str = unknown_error; } else str = _sys_errlist[errno]; 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.pad = pad; function_done = (*function)(s, &info, &args); if (function_done < 0) RETURN(-1); done += function_done; } } do_return:; if (our_buffer) { if (fflush(s) == EOF) return -1; s->__buffer = s->__bufp = s->__get_limit = s->__put_limit = NULL; s->__bufsize = 0; } return done; } #undef RETURN #define RETURN return static int DEFUN(printf_unknown, (s, type, info, arg), FILE *s AND CONST struct printf_info *info AND va_list *arg) { int done = 0; char work[BUFSIZ]; char *CONST workend = &work[sizeof(work) - 1]; register char *w; register int prec = info->prec, width = info->width; outchar('%'); if (info->alt) outchar('#'); if (info->showsign) outchar('+'); else if (info->space) outchar(' '); if (info->left) outchar('-'); if (info->pad == '0') outchar('0'); w = workend; while (width > 0) { *w-- = '0' + (width % 10); width /= 10; } while (++w <= workend) outchar(*w); if (info->prec != -1) { outchar('.'); w = workend; while (prec > 0) { *w-- = '0' + (prec % 10); prec /= 10; } while (++w <= workend) outchar(*w); } outchar(info->spec); return done; }