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