Reworked scanf() testing. General cleanups.
[fw/pdclib] / functions / _PDCLIB / scan.c
1 /* $Id$ */
2
3 /* _PDCLIB_scan( const char *, struct _PDCLIB_status_t * )
4
5    This file is part of the Public Domain C Library (PDCLib).
6    Permission is granted to use, modify, and / or redistribute at will.
7 */
8
9 #include <stdio.h>
10 #include <stdbool.h>
11 #include <stdlib.h>
12 #include <stdarg.h>
13 #include <stdint.h>
14 #include <ctype.h>
15 #include <string.h>
16 #include <stddef.h>
17 #include <limits.h>
18
19 /* Using an integer's bits as flags for both the conversion flags and length
20    modifiers.
21 */
22 #define E_suppressed 1<<0
23 #define E_char       1<<6
24 #define E_short      1<<7
25 #define E_long       1<<8
26 #define E_llong      1<<9
27 #define E_intmax     1<<10
28 #define E_size       1<<11
29 #define E_ptrdiff    1<<12
30 #define E_intptr     1<<13
31 #define E_ldouble    1<<14
32 #define E_unsigned   1<<16
33
34
35 /* Helper macro for assigning a readily converted integer value to the correct
36    parameter type, used in a switch on status->flags (see E_* flags above).
37    case_cond: combination of the E_* flags above, used for the switch-case
38    type:      integer type, used to get the correct type from the parameter
39               stack as well as for cast target.
40 */
41 #define ASSIGN_VALUE_TO( case_cond, type ) \
42     case case_cond: \
43         *( va_arg( status->arg, type * ) ) = (type)( value * sign ); \
44         break
45
46
47 /* Helper function to get a character from the string or stream, whatever is
48    used for input. When reading from a string, returns EOF on end-of-string
49    so that handling of the return value can be uniform for both streams and
50    strings.
51 */
52 static int GET( struct _PDCLIB_status_t * status )
53 {
54     int rc;
55     if ( status->stream != NULL )
56     {
57         rc = getc( status->stream );
58     }
59     else
60     {
61         rc = ( *status->s == '\0' ) ? EOF : (unsigned char)*((status->s)++);
62     }
63     if ( rc != EOF )
64     {
65         ++(status->i);
66         ++(status->current);
67     }
68     return rc;
69 }
70
71
72 /* Helper function to put a read character back into the string or stream,
73    whatever is used for input.
74 */
75 static void UNGET( int c, struct _PDCLIB_status_t * status )
76 {
77     if ( status->stream != NULL )
78     {
79         ungetc( c, status->stream ); /* TODO: Error? */
80     }
81     else
82     {
83         --(status->s);
84     }
85     --(status->i);
86     --(status->current);
87 }
88
89
90 /* Helper function to check if a character is part of a given scanset */
91 static bool IN_SCANSET( const char * scanlist, const char * end_scanlist, int rc )
92 {
93     // SOLAR
94     int previous = -1;
95     while ( scanlist != end_scanlist )
96     {
97         if ( ( *scanlist == '-' ) && ( previous != -1 ) )
98         {
99             /* possible scangroup ("a-z") */
100             if ( ++scanlist == end_scanlist )
101             {
102                 /* '-' at end of scanlist does not describe a scangroup */
103                 return rc == '-';
104             }
105             while ( ++previous <= (unsigned char)*scanlist )
106             {
107                 if ( previous == rc )
108                 {
109                     return true;
110                 }
111             }
112             previous = -1;
113         }
114         else
115         {
116             /* not a scangroup, check verbatim */
117             if ( rc == (unsigned char)*scanlist )
118             {
119                 return true;
120             }
121             previous = (unsigned char)(*scanlist++);
122         }
123     }
124     return false;
125 }
126
127
128 const char * _PDCLIB_scan( const char * spec, struct _PDCLIB_status_t * status )
129 {
130     /* generic input character */
131     int rc;
132     const char * orig_spec = spec;
133     if ( *(++spec) == '%' )
134     {
135         /* %% -> match single '%' */
136         rc = GET( status );
137         switch ( rc )
138         {
139             case EOF:
140                 /* input error */
141                 if ( status->n == 0 )
142                 {
143                     status->n = -1;
144                 }
145                 return NULL;
146             case '%':
147                 return ++spec;
148             default:
149                 UNGET( rc, status );
150                 break;
151         }
152     }
153     /* Initializing status structure */
154     status->flags = 0;
155     status->base = -1;
156     status->current = 0;
157     status->width = 0;
158     status->prec = 0;
159
160     /* '*' suppresses assigning parsed value to variable */
161     if ( *spec == '*' )
162     {
163         status->flags |= E_suppressed;
164         ++spec;
165     }
166
167     /* If a width is given, strtol() will return its value. If not given,
168        strtol() will return zero. In both cases, endptr will point to the
169        rest of the conversion specifier - just what we need.
170     */
171     char const * prev_spec = spec;
172     status->width = (int)strtol( spec, (char**)&spec, 10 );
173     if ( spec == prev_spec )
174     {
175         status->width = SIZE_MAX;
176     }
177
178     /* Optional length modifier
179        We step one character ahead in any case, and step back only if we find
180        there has been no length modifier (or step ahead another character if it
181        has been "hh" or "ll").
182     */
183     switch ( *(spec++) )
184     {
185         case 'h':
186             if ( *spec == 'h' )
187             {
188                 /* hh -> char */
189                 status->flags |= E_char;
190                 ++spec;
191             }
192             else
193             {
194                 /* h -> short */
195                 status->flags |= E_short;
196             }
197             break;
198         case 'l':
199             if ( *spec == 'l' )
200             {
201                 /* ll -> long long */
202                 status->flags |= E_llong;
203                 ++spec;
204             }
205             else
206             {
207                 /* l -> long */
208                 status->flags |= E_long;
209             }
210             break;
211         case 'j':
212             /* j -> intmax_t, which might or might not be long long */
213             status->flags |= E_intmax;
214             break;
215         case 'z':
216             /* z -> size_t, which might or might not be unsigned int */
217             status->flags |= E_size;
218             break;
219         case 't':
220             /* t -> ptrdiff_t, which might or might not be long */
221             status->flags |= E_ptrdiff;
222             break;
223         case 'L':
224             /* L -> long double */
225             status->flags |= E_ldouble;
226             break;
227         default:
228             --spec;
229             break;
230     }
231
232     /* Conversion specifier */
233
234     /* whether valid input had been parsed */
235     bool value_parsed = false;
236
237     switch ( *spec )
238     {
239         case 'd':
240             status->base = 10;
241             break;
242         case 'i':
243             status->base = 0;
244             break;
245         case 'o':
246             status->base = 8;
247             status->flags |= E_unsigned;
248             break;
249         case 'u':
250             status->base = 10;
251             status->flags |= E_unsigned;
252             break;
253         case 'x':
254             status->base = 16;
255             status->flags |= E_unsigned;
256             break;
257         case 'f':
258         case 'F':
259         case 'e':
260         case 'E':
261         case 'g':
262         case 'G':
263         case 'a':
264         case 'A':
265             break;
266         case 'c':
267         {
268             char * c = va_arg( status->arg, char * );
269             /* for %c, default width is one */
270             if ( status->width == SIZE_MAX )
271             {
272                 status->width = 1;
273             }
274             /* reading until width reached or input exhausted */
275             while ( ( status->current < status->width ) &&
276                     ( ( rc = GET( status ) ) != EOF ) )
277             {
278                 *(c++) = rc;
279                 value_parsed = true;
280             }
281             /* width or input exhausted */
282             if ( value_parsed )
283             {
284                 ++status->n;
285                 return ++spec;
286             }
287             else
288             {
289                 /* input error, no character read */
290                 if ( status->n == 0 )
291                 {
292                     status->n = -1;
293                 }
294                 return NULL;
295             }
296         }
297         case 's':
298         {
299             char * c = va_arg( status->arg, char * );
300             while ( ( status->current < status->width ) && 
301                     ( ( rc = GET( status ) ) != EOF ) )
302             {
303                 if ( isspace( rc ) )
304                 {
305                     UNGET( rc, status );
306                     if ( value_parsed )
307                     {
308                         /* matching sequence terminated by whitespace */
309                         *c = '\0';
310                         ++status->n;
311                         return ++spec;
312                     }
313                     else
314                     {
315                         /* matching error */
316                         return NULL;
317                     }
318                 }
319                 else
320                 {
321                     /* match */
322                     value_parsed = true;
323                     *(c++) = rc;
324                 }
325             }
326             /* width or input exhausted */
327             if ( value_parsed )
328             {
329                 *c = '\0';
330                 ++status->n;
331                 return ++spec;
332             }
333             else
334             {
335                 /* input error, no character read */
336                 if ( status->n == 0 )
337                 {
338                     status->n = -1;
339                 }
340                 return NULL;
341             }
342         }
343         case '[':
344         {
345             const char * endspec = spec;
346             bool negative_scanlist = false;
347             if ( *(++endspec) == '^' )
348             {
349                 negative_scanlist = true;
350                 ++endspec;
351             }
352             spec = endspec;
353             do
354             {
355                 // TODO: This can run beyond a malformed format string
356                 ++endspec;
357             } while ( *endspec != ']' );
358             // read according to scanlist, equiv. to %s above
359             char * c = va_arg( status->arg, char * );
360             while ( ( status->current < status->width ) && 
361                     ( ( rc = GET( status ) ) != EOF ) )
362             {
363                 if ( negative_scanlist )
364                 {
365                     if ( IN_SCANSET( spec, endspec, rc ) )
366                     {
367                         UNGET( rc, status );
368                         break;
369                     }
370                 }
371                 else
372                 {
373                     if ( ! IN_SCANSET( spec, endspec, rc ) )
374                     {
375                         UNGET( rc, status );
376                         break;
377                     }
378                 }
379                 value_parsed = true;
380                 *(c++) = rc;
381             }
382             if ( value_parsed )
383             {
384                 *c = '\0';
385                 ++status->n;
386                 return ++endspec;
387             }
388             else
389             {
390                 if ( rc == EOF )
391                 {
392                     status->n = -1;
393                 }
394                 return NULL;
395             }
396         }
397         case 'p':
398             status->base = 16;
399             status->flags |= E_unsigned;
400             break;
401         case 'n':
402         {
403             int * val = va_arg( status->arg, int * );
404             *val = status->i;
405             return ++spec;
406         }
407         default:
408             /* No conversion specifier. Bad conversion. */
409             return orig_spec;
410     }
411
412     if ( status->base != -1 )
413     {
414         /* integer conversion */
415         uintmax_t value = 0;         /* absolute value read */
416         bool prefix_parsed = false;
417         int sign = 0;
418         while ( ( status->current < status->width ) &&
419                 ( ( rc = GET( status ) ) != EOF ) )
420         {
421             if ( isspace( rc ) )
422             {
423                 if ( sign )
424                 {
425                     /* matching sequence terminated by whitespace */
426                     UNGET( rc, status );
427                     break;
428                 }
429                 else
430                 {
431                     /* leading whitespace not counted against width */
432                     status->current--;
433                 }
434             }
435             else if ( ! sign )
436             {
437                 /* no sign parsed yet */
438                 switch ( rc )
439                 {
440                     case '-':
441                         sign = -1;
442                         break;
443                     case '+':
444                         sign = 1;
445                         break;
446                     default:
447                         /* not a sign; put back character */
448                         sign = 1;
449                         UNGET( rc, status );
450                         break;
451                 }
452             }
453             else if ( ! prefix_parsed )
454             {
455                 /* no prefix (0x... for hex, 0... for octal) parsed yet */
456                 prefix_parsed = true;
457                 if ( rc != '0' )
458                 {
459                     /* not a prefix; if base not yet set, set to decimal */
460                     if ( status->base == 0 )
461                     {
462                         status->base = 10;
463                     }
464                     UNGET( rc, status );
465                 }
466                 else
467                 {
468                     /* starts with zero, so it might be a prefix. */
469                     /* check what follows next (might be 0x...) */
470                     if ( ( status->current < status->width ) &&
471                          ( ( rc = GET( status ) ) != EOF ) )
472                     {
473                         if ( tolower( rc ) == 'x' )
474                         {
475                             /* 0x... would be prefix for hex base... */
476                             if ( ( status->base == 0 ) ||
477                                  ( status->base == 16 ) )
478                             {
479                                 status->base = 16;
480                             }
481                             else
482                             {
483                                 /* ...unless already set to other value */
484                                 UNGET( rc, status );
485                                 value_parsed = true;
486                             }
487                         }
488                         else
489                         {
490                             /* 0... but not 0x.... would be octal prefix */
491                             UNGET( rc, status );
492                             if ( status->base == 0 )
493                             {
494                                 status->base = 8;
495                             }
496                             /* in any case we have read a zero */
497                             value_parsed = true;
498                         }
499                     }
500                     else
501                     {
502                         /* failed to read beyond the initial zero */
503                         value_parsed = true;
504                         break;
505                     }
506                 }
507             }
508             else
509             {
510                 char * digitptr = memchr( _PDCLIB_digits, tolower( rc ), status->base );
511                 if ( digitptr == NULL )
512                 {
513                     /* end of input item */
514                     UNGET( rc, status );
515                     break;
516                 }
517                 value *= status->base;
518                 value += digitptr - _PDCLIB_digits;
519                 value_parsed = true;
520             }
521         }
522         /* width or input exhausted, or non-matching character */
523         if ( ! value_parsed )
524         {
525             /* out of input before anything could be parsed - input error */
526             /* FIXME: if first character does not match, value_parsed is not set - but it is NOT an input error */
527             if ( ( status->n == 0 ) && ( rc == EOF ) )
528             {
529                 status->n = -1;
530             }
531             return NULL;
532         }
533         /* convert value to target type and assign to parameter */
534         switch ( status->flags & ( E_char | E_short | E_long | E_llong |
535                                    E_intmax | E_size | E_ptrdiff |
536                                    E_unsigned ) )
537         {
538             ASSIGN_VALUE_TO( E_char, char );
539             ASSIGN_VALUE_TO( E_char | E_unsigned, unsigned char );
540             ASSIGN_VALUE_TO( E_short, short );
541             ASSIGN_VALUE_TO( E_short | E_unsigned, unsigned short );
542             ASSIGN_VALUE_TO( 0, int );
543             ASSIGN_VALUE_TO( E_unsigned, unsigned int );
544             ASSIGN_VALUE_TO( E_long, long );
545             ASSIGN_VALUE_TO( E_long | E_unsigned, unsigned long );
546             ASSIGN_VALUE_TO( E_llong, long long );
547             ASSIGN_VALUE_TO( E_llong | E_unsigned, unsigned long long );
548             ASSIGN_VALUE_TO( E_intmax, intmax_t );
549             ASSIGN_VALUE_TO( E_intmax | E_unsigned, uintmax_t );
550             ASSIGN_VALUE_TO( E_size, size_t );
551             /* ASSIGN_VALUE_TO( E_size | E_unsigned, unsigned size_t ); */
552             ASSIGN_VALUE_TO( E_ptrdiff, ptrdiff_t );
553             /* ASSIGN_VALUE_TO( E_ptrdiff | E_unsigned, unsigned ptrdiff_t ); */
554             default:
555                 puts( "UNSUPPORTED SCANF FLAG COMBINATION" );
556                 return NULL; /* behaviour unspecified */
557         }
558         ++(status->n);
559         return ++spec;
560     }
561     /* TODO: Floats. */
562     return NULL;
563 }
564
565
566 #ifdef TEST
567 #define _PDCLIB_FILEID "_PDCLIB/scan.c"
568 #define _PDCLIB_STRINGIO
569
570 #include <_PDCLIB_test.h>
571
572 static int testscanf( char const * s, char const * format, ... )
573 {
574     struct _PDCLIB_status_t status;
575     status.n = 0;
576     status.i = 0;
577     status.s = (char *)s;
578     status.stream = NULL;
579     va_start( status.arg, format );
580     if ( *(_PDCLIB_scan( format, &status )) != '\0' )
581     {
582         printf( "_PDCLIB_scan() did not return end-of-specifier on '%s'.\n", format );
583         ++TEST_RESULTS;
584     }
585     va_end( status.arg );
586     return status.n;
587 }
588
589 #define TEST_CONVERSION_ONLY
590
591 int main( void )
592 {
593     char source[100];
594 #include "scanf_testcases.h"
595     return TEST_RESULTS;
596 }
597
598 #endif