]> pd.if.org Git - pdclib/blob - functions/stdio/_PDCLIB_scan.c
f3846b6967fdeff77c926637b51500cbec1b9735
[pdclib] / functions / stdio / _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 #ifndef REGTEST
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 = EOF;
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 = UINT_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             // TODO: Like _PDCLIB_print, E_pointer(?)
388             status->flags |= E_unsigned | E_long;
389             break;
390         case 'n':
391         {
392             int * val = va_arg( status->arg, int * );
393             *val = status->i;
394             return ++spec;
395         }
396         default:
397             /* No conversion specifier. Bad conversion. */
398             return orig_spec;
399     }
400
401     if ( status->base != -1 )
402     {
403         /* integer conversion */
404         uintmax_t value = 0;         /* absolute value read */
405         bool prefix_parsed = false;
406         int sign = 0;
407         while ( ( status->current < status->width ) &&
408                 ( ( rc = GET( status ) ) != EOF ) )
409         {
410             if ( isspace( rc ) )
411             {
412                 if ( sign )
413                 {
414                     /* matching sequence terminated by whitespace */
415                     UNGET( rc, status );
416                     break;
417                 }
418                 else
419                 {
420                     /* leading whitespace not counted against width */
421                     status->current--;
422                 }
423             }
424             else if ( ! sign )
425             {
426                 /* no sign parsed yet */
427                 switch ( rc )
428                 {
429                     case '-':
430                         sign = -1;
431                         break;
432                     case '+':
433                         sign = 1;
434                         break;
435                     default:
436                         /* not a sign; put back character */
437                         sign = 1;
438                         UNGET( rc, status );
439                         break;
440                 }
441             }
442             else if ( ! prefix_parsed )
443             {
444                 /* no prefix (0x... for hex, 0... for octal) parsed yet */
445                 prefix_parsed = true;
446                 if ( rc != '0' )
447                 {
448                     /* not a prefix; if base not yet set, set to decimal */
449                     if ( status->base == 0 )
450                     {
451                         status->base = 10;
452                     }
453                     UNGET( rc, status );
454                 }
455                 else
456                 {
457                     /* starts with zero, so it might be a prefix. */
458                     /* check what follows next (might be 0x...) */
459                     if ( ( status->current < status->width ) &&
460                          ( ( rc = GET( status ) ) != EOF ) )
461                     {
462                         if ( tolower( rc ) == 'x' )
463                         {
464                             /* 0x... would be prefix for hex base... */
465                             if ( ( status->base == 0 ) ||
466                                  ( status->base == 16 ) )
467                             {
468                                 status->base = 16;
469                             }
470                             else
471                             {
472                                 /* ...unless already set to other value */
473                                 UNGET( rc, status );
474                                 value_parsed = true;
475                             }
476                         }
477                         else
478                         {
479                             /* 0... but not 0x.... would be octal prefix */
480                             UNGET( rc, status );
481                             if ( status->base == 0 )
482                             {
483                                 status->base = 8;
484                             }
485                             /* in any case we have read a zero */
486                             value_parsed = true;
487                         }
488                     }
489                     else
490                     {
491                         /* failed to read beyond the initial zero */
492                         value_parsed = true;
493                         break;
494                     }
495                 }
496             }
497             else
498             {
499                 char * digitptr = memchr( _PDCLIB_digits, tolower( rc ), status->base );
500                 if ( digitptr == NULL )
501                 {
502                     /* end of input item */
503                     UNGET( rc, status );
504                     break;
505                 }
506                 value *= status->base;
507                 value += digitptr - _PDCLIB_digits;
508                 value_parsed = true;
509             }
510         }
511         /* width or input exhausted, or non-matching character */
512         if ( ! value_parsed )
513         {
514             /* out of input before anything could be parsed - input error */
515             /* FIXME: if first character does not match, value_parsed is not set - but it is NOT an input error */
516             if ( ( status->n == 0 ) && ( rc == EOF ) )
517             {
518                 status->n = -1;
519             }
520             return NULL;
521         }
522         /* convert value to target type and assign to parameter */
523         if ( ! ( status->flags & E_suppressed ) )
524         {
525             switch ( status->flags & ( E_char | E_short | E_long | E_llong |
526                                        E_intmax | E_size | E_ptrdiff |
527                                        E_unsigned ) )
528             {
529                 case E_char:
530                     *( va_arg( status->arg,               char * ) ) =               (char)( value * sign );
531                     break;
532                 case E_char | E_unsigned:
533                     *( va_arg( status->arg,      unsigned char * ) ) =      (unsigned char)( value * sign );
534                     break;
535
536                 case E_short:
537                     *( va_arg( status->arg,              short * ) ) =              (short)( value * sign );
538                     break;
539                 case E_short | E_unsigned:
540                     *( va_arg( status->arg,     unsigned short * ) ) =     (unsigned short)( value * sign );
541                     break;
542
543                 case 0:
544                     *( va_arg( status->arg,                int * ) ) =                (int)( value * sign );
545                     break;
546                 case E_unsigned:
547                     *( va_arg( status->arg,       unsigned int * ) ) =       (unsigned int)( value * sign );
548                     break;
549
550                 case E_long:
551                     *( va_arg( status->arg,               long * ) ) =               (long)( value * sign );
552                     break;
553                 case E_long | E_unsigned:
554                     *( va_arg( status->arg,      unsigned long * ) ) =      (unsigned long)( value * sign );
555                     break;
556
557                 case E_llong:
558                     *( va_arg( status->arg,          long long * ) ) =          (long long)( value * sign );
559                     break;
560                 case E_llong | E_unsigned:
561                     *( va_arg( status->arg, unsigned long long * ) ) = (unsigned long long)( value * sign );
562                     break;
563
564                 case E_intmax:
565                     *( va_arg( status->arg,           intmax_t * ) ) =           (intmax_t)( value * sign );
566                     break;
567                 case E_intmax | E_unsigned:
568                     *( va_arg( status->arg,          uintmax_t * ) ) =          (uintmax_t)( value * sign );
569                     break;
570
571                 case E_size:
572                     /* E_size always implies unsigned */
573                     *( va_arg( status->arg,             size_t * ) ) =             (size_t)( value * sign );
574                     break;
575
576                 case E_ptrdiff:
577                     /* E_ptrdiff always implies signed */
578                     *( va_arg( status->arg,          ptrdiff_t * ) ) =          (ptrdiff_t)( value * sign );
579                     break;
580
581                 default:
582                     puts( "UNSUPPORTED SCANF FLAG COMBINATION" );
583                     return NULL; /* behaviour unspecified */
584             }
585             ++(status->n);
586         }
587         return ++spec;
588     }
589     /* TODO: Floats. */
590     return NULL;
591 }
592 #endif
593
594 #ifdef TEST
595 #define _PDCLIB_FILEID "_PDCLIB/scan.c"
596 #define _PDCLIB_STRINGIO
597
598 #include <_PDCLIB_test.h>
599
600 #ifndef REGTEST
601 static int testscanf( char const * s, char const * format, ... )
602 {
603     struct _PDCLIB_status_t status;
604     status.n = 0;
605     status.i = 0;
606     status.s = (char *)s;
607     status.stream = NULL;
608     va_start( status.arg, format );
609     if ( *(_PDCLIB_scan( format, &status )) != '\0' )
610     {
611         printf( "_PDCLIB_scan() did not return end-of-specifier on '%s'.\n", format );
612         ++TEST_RESULTS;
613     }
614     va_end( status.arg );
615     return status.n;
616 }
617 #endif
618
619 #define TEST_CONVERSION_ONLY
620
621 int main( void )
622 {
623 #ifndef REGTEST
624     char source[100];
625 #include "scanf_testcases.h"
626 #endif
627     return TEST_RESULTS;
628 }
629
630 #endif