/* Copyright (C) 1991, 1992 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 #ifdef __GNUC__ #define HAVE_LONGLONG #define LONGLONG long long #else #define LONGLONG long #endif #define inchar() ((c = getc(s)) == EOF ? EOF : (++read_in, c)) #define conv_error() return((c == EOF || ungetc(c, s)), done) #define input_error() return(-1) #define memory_error() return((errno = ENOMEM), EOF) /* Read formatted input from S according to the format string FORMAT, using the argument list in ARG. Return the number of assignments made, or -1 for an input error. */ int DEFUN(__vfscanf, (s, format, arg), FILE *s AND CONST char *format AND PTR argptr) { va_list arg = (va_list) argptr; register CONST char *f = format; register char fc; /* Current character of the format. */ register size_t done = 0; /* Assignments done. */ register size_t read_in = 0; /* Chars read in. */ register int c; /* Last char read. */ register int do_assign; /* Whether to do an assignment. */ register int width; /* Maximum field width. */ /* 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 /* Status for reading F-P nums. */ char got_dot, got_e; /* If a [...] is a [^...]. */ char not_in; /* Base for integral numbers. */ int base; /* Integral holding variables. */ long int num; unsigned long int unum; /* Floating-point holding variable. */ LONG_DOUBLE fp_num; /* Character-buffer pointer. */ register char *str; /* Workspace. */ char work[200]; char *w; /* Pointer into WORK. */ wchar_t decimal; /* Decimal point character. */ if (!__validfp(s) || !s->__mode.__read || format == NULL) { errno = EINVAL; input_error(); } /* Figure out the decimal point character. */ if (mbtowc(&decimal, _numeric_info->decimal_point, strlen(_numeric_info->decimal_point)) <= 0) decimal = (wchar_t) *_numeric_info->decimal_point; c = inchar(); /* Run through the format string. */ while (*f != '\0') { if (!isascii(*f)) { /* Non-ASCII, may be a multibyte. */ int len = mblen(f, strlen(f)); if (len > 0) { while (len-- > 0) if (c == EOF) input_error(); else if (c == *f++) (void) inchar(); else conv_error(); continue; } } fc = *f++; if (fc != '%') { /* Characters other than format specs must just match. */ if (c == EOF) input_error(); if (isspace(fc)) { /* Whitespace characters match any amount of whitespace. */ while (isspace (c)) inchar (); continue; } else if (c == fc) (void) inchar(); else conv_error(); continue; } /* Check for the assignment-suppressant. */ if (*f == '*') { do_assign = 0; ++f; } else do_assign = 1; /* Find the maximum field width. */ width = 0; while (isdigit(*f)) { width *= 10; width += *f++ - '0'; } if (width == 0) width = -1; /* Check for type modifiers. */ is_short = is_long = is_long_double = 0; while (*f == 'h' || *f == 'l' || *f == 'L') switch (*f++) { case 'h': /* int's are short int's. */ is_short = 1; break; case 'l': if (is_long) /* A double `l' is equivalent to an `L'. */ is_longlong = 1; else /* 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; } /* End of the format string? */ if (*f == '\0') conv_error(); /* Find the conversion specifier. */ w = work; fc = *f++; if (fc != '[' && fc != 'c' && fc != 'n') /* Eat whitespace. */ while (isspace(c)) (void) inchar(); switch (fc) { case '%': /* Must match a literal '%'. */ if (c != fc) conv_error(); break; case 'n': /* Answer number of assignments done. */ if (do_assign) *va_arg(arg, int *) = read_in; break; case 'c': /* Match characters. */ if (do_assign) { str = va_arg(arg, char *); if (str == NULL) conv_error(); } if (c == EOF) input_error(); if (width == -1) width = 1; if (do_assign) while (inchar() != EOF && width-- > 0) *str++ = c; else while (inchar() != EOF && width-- > 0) ; if (do_assign) ++done; if (c == EOF) input_error(); break; case 's': /* Read a string. */ if (do_assign) { str = va_arg(arg, char *); if (str == NULL) conv_error(); } if (c == EOF) input_error(); do { if (isspace(c)) break; if (do_assign) *str++ = c; if (width > 0 && --width == 0) break; } while (inchar() != EOF); if (do_assign) { *str = '\0'; ++done; } break; case 'x': /* Hexadecimal integer. */ case 'X': /* Ditto. */ base = 16; goto number; case 'o': /* Octal integer. */ base = 8; goto number; case 'u': /* Decimal integer. */ case 'd': /* Ditto. */ base = 10; goto number; case 'i': /* Generic number. */ base = 0; number:; if (c == EOF) input_error(); /* Check for a sign. */ if (c == '-' || c == '+') { *w++ = c; if (width > 0) --width; (void) inchar(); } /* Look for a leading indication of base. */ if (c == '0') { if (width > 0) --width; *w++ = '0'; (void) inchar(); if (tolower(c) == 'x') { if (base == 0) base = 16; if (base == 16) { if (width > 0) --width; (void) inchar(); } } else if (base == 0) base = 8; } if (base == 0) base = 10; /* Read the number into WORK. */ do { if (base == 16 ? !isxdigit(c) : (!isdigit(c) || c - '0' >= base)) break; *w++ = c; if (width > 0) --width; } while (inchar() != EOF && width != 0); if (w == work || (w - work == 1 && (work[0] == '+' || work[0] == '-'))) /* There was on number. */ conv_error(); /* Convert the number. */ *w = '\0'; num = strtol(work, &w, base); if (w == work) conv_error(); if (do_assign) { if (is_longlong) *va_arg(arg, LONGLONG int *) = num; else if (is_long) *va_arg(arg, long int *) = num; else if (is_short) *va_arg(arg, short int *) = (short int) num; else *va_arg(arg, int *) = (int) num; ++done; } break; case 'e': /* Floating-point numbers. */ case 'E': case 'f': case 'g': case 'G': if (c == EOF) input_error(); /* Check for a sign. */ if (c == '-' || c == '+') { *w++ = c; if (inchar() == EOF) input_error(); if (width > 0) --width; } got_dot = got_e = 0; do { if (isdigit(c)) *w++ = c; else if (got_e && w[-1] == 'e' && (c == '-' || c == '+')) *w++ = c; else if (!got_e && tolower(c) == 'e') { *w++ = 'e'; got_e = got_dot = 1; } else if (c == decimal && !got_dot) { *w++ = c; got_dot = 1; } else break; if (width > 0) --width; } while (inchar() != EOF && width != 0); if (w == work) conv_error(); if (w[-1] == '-' || w[-1] == '+' || w[-1] == 'e') conv_error(); /* Convert the number. */ *w = '\0'; fp_num = strtod(work, &w); if (w == work) conv_error(); if (do_assign) { if (is_long_double) *va_arg(arg, LONG_DOUBLE *) = fp_num; else if (is_long) *va_arg(arg, double *) = (double) fp_num; else *va_arg(arg, float *) = (float) fp_num; ++done; } break; case '[': /* Character class. */ if (do_assign) { str = va_arg(arg, char *); if (str == NULL) conv_error(); } if (c == EOF) input_error(); if (*f == '^') { ++f; not_in = 1; } else not_in = 0; while ((fc = *f++) != '\0' && fc != ']') { if (fc == '-' && *f != '\0' && *f != ']' && w > work && w[-1] <= *f) /* Add all characters from the one before the '-' up to (but not including) the next format char. */ for (fc = w[-1] + 1; fc < *f; ++fc) *w++ = fc; else /* Add the character to the list. */ *w++ = fc; } if (fc == '\0') conv_error(); *w = '\0'; unum = read_in; do { if ((strchr(work, c) == NULL) != not_in) break; if (do_assign) *str++ = c; if (width > 0) --width; } while (inchar() != EOF && width != 0); if (read_in == unum) conv_error(); if (do_assign) { *str = '\0'; ++done; } break; case 'p': /* Generic pointer. */ base = 16; /* A PTR must be the same size as a `long int'. */ is_long = 1; goto number; } } conv_error(); }