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