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