/* Copyright (C) 1991 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. */ /* * adapted for atariST gcc lib * * NOTES: interface is different from equivalent gnuC lib function * it was munged to match our old _scanf(). * * if __NO_FLOAT__ is defined then the floating point stuff * gets nuked (for iio*.olb) as per our old scanf.c. * * It is very important to read and understand the GNU Library General * Public License. It specifies rights and conditions that are different * from the GNU copyleft. * * ++jrb bammi@cadence.com */ #include #include #include #include #include #include #include /* the code assumes this definition, note: traditional def * of tolower will break the code. Ansi def should be ok. */ #ifdef tolower # undef tolower # define tolower(c) (isupper(c) ? (c)^0x20 : (c)) #endif #ifndef __NO_FLOAT__ # define FLOATS 1 #else # define FLOATS 0 #endif #define TEN_MUL(X) ((((X) << 2) + (X)) << 1) #ifdef __GNUC__ #define HAVE_LONGLONG #define LONGLONG long long #else #define LONGLONG long #endif #define inchar() ((c = ((*get)(s))) == EOF ? EOF : (++read_in, c)) #define unchar(c) (--read_in, (*unget)(c, s)) #define conv_error() return((c == EOF || ((*unget)(c, s))), done) #define input_error() return( done < 1 ? EOF : done ) #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 _scanf(s, get, unget, format, arg) register FILE *s; int (*get) __PROTO((FILE *)); int (*unget) __PROTO((int, FILE *)); const char *format; va_list arg; { 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 #if FLOATS /* Status for reading F-P nums. */ char got_dot, got_e; #endif /* If a [...] is a [^...]. */ char not_in; /* Base for integral numbers. */ int base; /* Integral holding variables. */ long int num; unsigned long int unum; #if FLOATS /* Floating-point holding variable. */ long double fp_num; #endif /* Character-buffer pointer. */ register char *str; /* Workspace. */ char work[256]; char *w; /* Pointer into WORK. */ if ((format == NULL) || (!*format)) { errno = EINVAL; input_error(); } # define decimal ('.') /* should really come from locale stuff that we dont */ /* have as yet */ c = inchar(); /* Run through the format string. */ while (*f != '\0') { #if 0 /* no mb support as yet */ 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; } } #endif 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)) (void)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': #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; } /* 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(); else c = inchar(); break; case 'n': /* Answer number of assignments done. */ if (do_assign) *va_arg(arg, int *) = read_in - 1; /* -1 is debatable ++jrb */ 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; /* mjr: */ if (do_assign) { do *str++ = c; while (inchar() != EOF && --width > 0); } 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; } if (c == EOF) input_error(); 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') { /* one char look ahead to see if its really a lead ind */ int savec = c; int peekc = inchar(); c = savec; (void)unchar(peekc); if(isxdigit(peekc)) { if (base == 0) base = 16; if (base == 16) { if (width > 0) --width; (void) inchar(); } } } else if (base == 0) if((c >= '0') && (c <= '7')) 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 no 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; #if FLOATS 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 0 if (w[-1] == '-' || w[-1] == '+' || w[-1] == 'e') conv_error(); #else if (w[-1] == '-' || w[-1] == '+') conv_error(); if(tolower(w[-1]) == 'e') { unchar(c); --w; c = *w; } #endif /* 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; #endif /* FLOATS */ 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; w = (char *)f; /* remember start of class */ bzero (work, 256); while ((fc = *f++) != '\0' && (fc != ']' || f - 1 == w)) { /* Add the character to the list. */ work[(unsigned char)fc] = 1; /* Look ahead for a range. */ if (f[0] == '-' && f[1] != '\0' && f[1] != ']') { /* Add all characters from the one before the '-' up to the next format char. */ unsigned char end = f[1]; while ((unsigned char)++fc <= end) work[(unsigned char)fc] = 1; f += 2; } } if (fc == '\0') conv_error(); work[0] = not_in; unum = read_in; do { if (work[(unsigned char)c] == 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(); }