]> pd.if.org Git - pdclib/blob - functions/_PDCLIB/scan.c
Fixed pointer handling of printf() / scanf().
[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_pointer    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             status->flags |= E_pointer;
386             break;
387         case 'n':
388         {
389             int * val = va_arg( status->arg, int * );
390             *val = status->i;
391             return ++spec;
392         }
393         default:
394             /* No conversion specifier. Bad conversion. */
395             return orig_spec;
396     }
397
398     if ( status->base != -1 )
399     {
400         /* integer conversion */
401         uintmax_t value = 0;         /* absolute value read */
402         bool prefix_parsed = false;
403         int sign = 0;
404         while ( ( status->current < status->width ) &&
405                 ( ( rc = GET( status ) ) != EOF ) )
406         {
407             if ( isspace( rc ) )
408             {
409                 if ( sign )
410                 {
411                     /* matching sequence terminated by whitespace */
412                     UNGET( rc, status );
413                     break;
414                 }
415                 else
416                 {
417                     /* leading whitespace not counted against width */
418                     status->current--;
419                 }
420             }
421             else if ( ! sign )
422             {
423                 /* no sign parsed yet */
424                 switch ( rc )
425                 {
426                     case '-':
427                         sign = -1;
428                         break;
429                     case '+':
430                         sign = 1;
431                         break;
432                     default:
433                         /* not a sign; put back character */
434                         sign = 1;
435                         UNGET( rc, status );
436                         break;
437                 }
438             }
439             else if ( ! prefix_parsed )
440             {
441                 /* no prefix (0x... for hex, 0... for octal) parsed yet */
442                 prefix_parsed = true;
443                 if ( rc != '0' )
444                 {
445                     /* not a prefix; if base not yet set, set to decimal */
446                     if ( status->base == 0 )
447                     {
448                         status->base = 10;
449                     }
450                     UNGET( rc, status );
451                 }
452                 else
453                 {
454                     /* starts with zero, so it might be a prefix. */
455                     /* check what follows next (might be 0x...) */
456                     if ( ( status->current < status->width ) &&
457                          ( ( rc = GET( status ) ) != EOF ) )
458                     {
459                         if ( tolower( rc ) == 'x' )
460                         {
461                             /* 0x... would be prefix for hex base... */
462                             if ( ( status->base == 0 ) ||
463                                  ( status->base == 16 ) )
464                             {
465                                 status->base = 16;
466                             }
467                             else
468                             {
469                                 /* ...unless already set to other value */
470                                 UNGET( rc, status );
471                                 value_parsed = true;
472                             }
473                         }
474                         else
475                         {
476                             /* 0... but not 0x.... would be octal prefix */
477                             UNGET( rc, status );
478                             if ( status->base == 0 )
479                             {
480                                 status->base = 8;
481                             }
482                             /* in any case we have read a zero */
483                             value_parsed = true;
484                         }
485                     }
486                     else
487                     {
488                         /* failed to read beyond the initial zero */
489                         value_parsed = true;
490                         break;
491                     }
492                 }
493             }
494             else
495             {
496                 char * digitptr = memchr( _PDCLIB_digits, tolower( rc ), status->base );
497                 if ( digitptr == NULL )
498                 {
499                     /* end of input item */
500                     UNGET( rc, status );
501                     break;
502                 }
503                 value *= status->base;
504                 value += digitptr - _PDCLIB_digits;
505                 value_parsed = true;
506             }
507         }
508         /* width or input exhausted, or non-matching character */
509         if ( ! value_parsed )
510         {
511             /* out of input before anything could be parsed - input error */
512             /* FIXME: if first character does not match, value_parsed is not set - but it is NOT an input error */
513             if ( ( status->n == 0 ) && ( rc == EOF ) )
514             {
515                 status->n = -1;
516             }
517             return NULL;
518         }
519         /* convert value to target type and assign to parameter */
520         if ( ! ( status->flags & E_suppressed ) )
521         {
522             switch ( status->flags & ( E_char | E_short | E_long | E_llong |
523                                        E_intmax | E_size | E_ptrdiff | E_pointer |
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                 case E_pointer:
579                     /* E_pointer always implies unsigned */
580                     *( uintptr_t* )( va_arg( status->arg, void * ) ) =          (uintptr_t)( value * sign );
581                     break;
582
583                 default:
584                     puts( "UNSUPPORTED SCANF FLAG COMBINATION" );
585                     return NULL; /* behaviour unspecified */
586             }
587             ++(status->n);
588         }
589         return ++spec;
590     }
591     /* TODO: Floats. */
592     return NULL;
593 }
594
595
596 #ifdef TEST
597 #define _PDCLIB_FILEID "_PDCLIB/scan.c"
598 #define _PDCLIB_STRINGIO
599
600 #include <_PDCLIB_test.h>
601
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
619 #define TEST_CONVERSION_ONLY
620
621 int main( void )
622 {
623     char source[100];
624 #include "scanf_testcases.h"
625     return TEST_RESULTS;
626 }
627
628 #endif