4b64d773465f937f5b9fc5c25611193515894b40
[kopensolaris-gnu/glibc.git] / stdio / vfscanf.c
1 /* Copyright (C) 1991, 1992 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
3
4 The GNU C Library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public License as
6 published by the Free Software Foundation; either version 2 of the
7 License, or (at your option) any later version.
8
9 The GNU C Library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 Library General Public License for more details.
13
14 You should have received a copy of the GNU Library General Public
15 License along with the GNU C Library; see the file COPYING.LIB.  If
16 not, write to the Free Software Foundation, Inc., 675 Mass Ave,
17 Cambridge, MA 02139, USA.  */
18
19 #include <ansidecl.h>
20 #include <localeinfo.h>
21 #include <errno.h>
22 #include <limits.h>
23 #include <ctype.h>
24 #include <stdarg.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28
29
30 #ifdef  __GNUC__
31 #define HAVE_LONGLONG
32 #define LONGLONG        long long
33 #else
34 #define LONGLONG        long
35 #endif
36
37
38 #define inchar()        ((c = getc(s)) == EOF ? EOF : (++read_in, c))
39 #define conv_error()    return((c == EOF || ungetc(c, s)), done)
40 #define input_error()   return(-1)
41 #define memory_error()  return((errno = ENOMEM), EOF)
42
43
44 /* Read formatted input from S according to the format string
45    FORMAT, using the argument list in ARG.
46    Return the number of assignments made, or -1 for an input error.  */
47 int
48 DEFUN(__vfscanf, (s, format, arg),
49       FILE *s AND CONST char *format AND va_list argptr)
50 {
51   va_list arg = (va_list) argptr;
52
53   register CONST char *f = format;
54   register char fc;             /* Current character of the format.  */
55   register size_t done = 0;     /* Assignments done.  */
56   register size_t read_in = 0;  /* Chars read in.  */
57   register int c;               /* Last char read.  */
58   register int do_assign;       /* Whether to do an assignment.  */
59   register int width;           /* Maximum field width.  */
60
61   /* Type modifiers.  */
62   char is_short, is_long, is_long_double;
63 #ifdef  HAVE_LONGLONG
64       /* We use the `L' modifier for `long long int'.  */
65 #define is_longlong     is_long_double
66 #else
67 #define is_longlong     0
68 #endif
69   /* Status for reading F-P nums.  */
70   char got_dot, got_e;
71   /* If a [...] is a [^...].  */
72   char not_in;
73   /* Base for integral numbers.  */
74   int base;
75   /* Integral holding variables.  */
76   long int num;
77   unsigned long int unum;
78   /* Floating-point holding variable.  */
79   LONG_DOUBLE fp_num;
80   /* Character-buffer pointer.  */
81   register char *str;
82   /* Workspace.  */
83   char work[200];
84   char *w;              /* Pointer into WORK.  */
85   wchar_t decimal;      /* Decimal point character.  */
86
87   if (!__validfp(s) || !s->__mode.__read || format == NULL)
88     {
89       errno = EINVAL;
90       input_error();
91     }
92
93   /* Figure out the decimal point character.  */
94   if (mbtowc(&decimal, _numeric_info->decimal_point,
95              strlen(_numeric_info->decimal_point)) <= 0)
96     decimal = (wchar_t) *_numeric_info->decimal_point;
97
98   c = inchar();
99
100   /* Run through the format string.  */
101   while (*f != '\0')
102     {
103       if (!isascii(*f))
104         {
105           /* Non-ASCII, may be a multibyte.  */
106           int len = mblen(f, strlen(f));
107           if (len > 0)
108             {
109               while (len-- > 0)
110                 if (c == EOF)
111                   input_error();
112                 else if (c == *f++)
113                   (void) inchar();
114                 else
115                   conv_error();
116               continue;
117             }
118         }
119
120       fc = *f++;
121       if (fc != '%')
122         {
123           /* Characters other than format specs must just match.  */
124           if (c == EOF)
125             input_error();
126           if (isspace(fc))
127             {
128               /* Whitespace characters match any amount of whitespace.  */
129               while (isspace (c))
130                 inchar ();
131               continue;
132             }
133           else if (c == fc)
134             (void) inchar();
135           else
136             conv_error();
137           continue;
138         }
139
140       /* Check for the assignment-suppressant.  */
141       if (*f == '*')
142         {
143           do_assign = 0;
144           ++f;
145         }
146       else
147         do_assign = 1;
148                 
149       /* Find the maximum field width.  */
150       width = 0;
151       while (isdigit(*f))
152         {
153           width *= 10;
154           width += *f++ - '0';
155         }
156       if (width == 0)
157         width = -1;
158
159       /* Check for type modifiers.  */
160       is_short = is_long = is_long_double = 0;
161       while (*f == 'h' || *f == 'l' || *f == 'L')
162         switch (*f++)
163           {
164           case 'h':
165             /* int's are short int's.  */
166             is_short = 1;
167             break;
168           case 'l':
169             if (is_long)
170               /* A double `l' is equivalent to an `L'.  */
171               is_longlong = 1;
172             else
173               /* int's are long int's.  */
174               is_long = 1;
175             break;
176           case 'L':
177             /* double's are long double's, and int's are long long int's.  */
178             is_long_double = 1;
179             break;
180           }
181
182       /* End of the format string?  */
183       if (*f == '\0')
184         conv_error();
185
186       /* Find the conversion specifier.  */
187       w = work;
188       fc = *f++;
189       if (fc != '[' && fc != 'c' && fc != 'n')
190         /* Eat whitespace.  */
191         while (isspace(c))
192           (void) inchar();
193       switch (fc)
194         {
195         case '%':       /* Must match a literal '%'.  */
196           if (c != fc)
197             conv_error();
198           break;
199
200         case 'n':       /* Answer number of assignments done.  */
201           if (do_assign)
202             *va_arg(arg, int *) = read_in;
203           break;
204
205         case 'c':       /* Match characters.  */
206           if (do_assign)
207             {
208               str = va_arg(arg, char *);
209               if (str == NULL)
210                 conv_error();
211             }
212
213           if (c == EOF)
214             input_error();
215
216           if (width == -1)
217             width = 1;
218
219           if (do_assign)
220             {
221               do
222                 *str++ = c;
223               while (inchar() != EOF && width-- > 0);
224             }
225           else
226             while (inchar() != EOF && width > 0)
227               --width;
228
229           if (do_assign)
230             ++done;
231
232           if (c == EOF)
233             input_error();
234           break;
235
236         case 's':       /* Read a string.  */
237           if (do_assign)
238             {
239               str = va_arg(arg, char *);
240               if (str == NULL)
241                 conv_error();
242             }
243
244           if (c == EOF)
245             input_error();
246
247           do
248             {
249               if (isspace(c))
250                 break;
251               if (do_assign)
252                 *str++ = c;
253             } while (inchar() != EOF && (width <= 0 || --width > 0));
254
255           if (do_assign)
256             {
257               *str = '\0';
258               ++done;
259             }
260           break;
261
262         case 'x':       /* Hexadecimal integer.  */
263         case 'X':       /* Ditto.  */ 
264           base = 16;
265           goto number;
266
267         case 'o':       /* Octal integer.  */
268           base = 8;
269           goto number;
270
271         case 'u':       /* Decimal integer.  */
272         case 'd':       /* Ditto.  */
273           base = 10;
274           goto number;
275
276         case 'i':       /* Generic number.  */
277           base = 0;
278
279         number:;
280           if (c == EOF)
281             input_error();
282
283           /* Check for a sign.  */
284           if (c == '-' || c == '+')
285             {
286               *w++ = c;
287               if (width > 0)
288                 --width;
289               (void) inchar();
290             }
291
292           /* Look for a leading indication of base.  */
293           if (c == '0')
294             {
295               if (width > 0)
296                 --width;
297               *w++ = '0';
298
299               (void) inchar();
300
301               if (tolower(c) == 'x')
302                 {
303                   if (base == 0)
304                     base = 16;
305                   if (base == 16)
306                     {
307                       if (width > 0)
308                         --width;
309                       (void) inchar();
310                     }
311                 }
312               else if (base == 0)
313                 base = 8;
314             }
315
316           if (base == 0)
317             base = 10;
318
319           /* Read the number into WORK.  */
320           do
321             {
322               if (base == 16 ? !isxdigit(c) :
323                   (!isdigit(c) || c - '0' >= base))
324                 break;
325               *w++ = c;
326               if (width > 0)
327                 --width;
328             } while (inchar() != EOF && width != 0);
329
330           if (w == work ||
331               (w - work == 1 && (work[0] == '+' || work[0] == '-')))
332             /* There was on number.  */
333             conv_error();
334
335           /* Convert the number.  */
336           *w = '\0';
337           num = strtol(work, &w, base);
338           if (w == work)
339             conv_error();
340
341           if (do_assign)
342             {
343               if (is_longlong)
344                 *va_arg(arg, LONGLONG int *) = num;
345               else if (is_long)
346                 *va_arg(arg, long int *) = num;
347               else if (is_short)
348                 *va_arg(arg, short int *) = (short int) num;
349               else
350                 *va_arg(arg, int *) = (int) num;
351               ++done;
352             }
353           break;
354
355         case 'e':       /* Floating-point numbers.  */
356         case 'E':
357         case 'f':
358         case 'g':
359         case 'G':
360           if (c == EOF)
361             input_error();
362
363           /* Check for a sign.  */
364           if (c == '-' || c == '+')
365             {
366               *w++ = c;
367               if (inchar() == EOF)
368                 input_error();
369               if (width > 0)
370                 --width;
371             }
372
373           got_dot = got_e = 0;
374           do
375             {
376               if (isdigit(c))
377                 *w++ = c;
378               else if (got_e && w[-1] == 'e' && (c == '-' || c == '+'))
379                 *w++ = c;
380               else if (!got_e && tolower(c) == 'e')
381                 {
382                   *w++ = 'e';
383                   got_e = got_dot = 1;
384                 }
385               else if (c == decimal && !got_dot)
386                 {
387                   *w++ = c;
388                   got_dot = 1;
389                 }
390               else
391                 break;
392               if (width > 0)
393                 --width;
394             } while (inchar() != EOF && width != 0);
395
396           if (w == work)
397             conv_error();
398           if (w[-1] == '-' || w[-1] == '+' || w[-1] == 'e')
399             conv_error();
400
401           /* Convert the number.  */
402           *w = '\0';
403           fp_num = strtod(work, &w);
404           if (w == work)
405             conv_error();
406
407           if (do_assign)
408             {
409               if (is_long_double)
410                 *va_arg(arg, LONG_DOUBLE *) = fp_num;
411               else if (is_long)
412                 *va_arg(arg, double *) = (double) fp_num;
413               else
414                 *va_arg(arg, float *) = (float) fp_num;
415               ++done;
416             }
417           break;
418
419         case '[':       /* Character class.  */
420           if (do_assign)
421             {
422               str = va_arg(arg, char *);
423               if (str == NULL)
424                 conv_error();
425             }
426
427           if (c == EOF)
428             input_error();
429
430           if (*f == '^')
431             {
432               ++f;
433               not_in = 1;
434             }
435           else
436             not_in = 0;
437
438           while ((fc = *f++) != '\0' && fc != ']')
439             {
440               if (fc == '-' && *f != '\0' && *f != ']' &&
441                   w > work && w[-1] <= *f)
442                 /* Add all characters from the one before the '-'
443                    up to (but not including) the next format char.  */
444                 for (fc = w[-1] + 1; fc < *f; ++fc)
445                   *w++ = fc;
446               else
447                 /* Add the character to the list.  */
448                 *w++ = fc;
449             }
450           if (fc == '\0')
451             conv_error();
452
453           *w = '\0';
454           unum = read_in;
455           do
456             {
457               if ((strchr(work, c) == NULL) != not_in)
458                 break;
459               if (do_assign)
460                 *str++ = c;
461               if (width > 0)
462                 --width;
463             } while (inchar() != EOF && width != 0);
464           if (read_in == unum)
465             conv_error();
466
467           if (do_assign)
468             {
469               *str = '\0';
470               ++done;
471             }
472           break;
473
474         case 'p':       /* Generic pointer.  */
475           base = 16;
476           /* A PTR must be the same size as a `long int'.  */
477           is_long = 1;
478           goto number;
479         }
480     }
481
482   conv_error();
483 }