3 /* _PDCLIB_scan( const char *, struct _PDCLIB_status_t * )
5 This file is part of the Public Domain C Library (PDCLib).
6 Permission is granted to use, modify, and / or redistribute at will.
19 /* Using an integer's bits as flags for both the conversion flags and length
22 #define E_suppressed 1<<0
27 #define E_intmax 1<<10
29 #define E_ptrdiff 1<<12
30 #define E_intptr 1<<13
31 #define E_ldouble 1<<14
32 #define E_unsigned 1<<16
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
40 static int GET( struct _PDCLIB_status_t * status )
43 if ( status->stream != NULL )
45 rc = getc( status->stream );
49 rc = ( *status->s == '\0' ) ? EOF : (unsigned char)*((status->s)++);
60 /* Helper function to put a read character back into the string or stream,
61 whatever is used for input.
63 static void UNGET( int c, struct _PDCLIB_status_t * status )
65 if ( status->stream != NULL )
67 ungetc( c, status->stream ); /* TODO: Error? */
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 )
83 while ( scanlist != end_scanlist )
85 if ( ( *scanlist == '-' ) && ( previous != -1 ) )
87 /* possible scangroup ("a-z") */
88 if ( ++scanlist == end_scanlist )
90 /* '-' at end of scanlist does not describe a scangroup */
93 while ( ++previous <= (unsigned char)*scanlist )
104 /* not a scangroup, check verbatim */
105 if ( rc == (unsigned char)*scanlist )
109 previous = (unsigned char)(*scanlist++);
116 const char * _PDCLIB_scan( const char * spec, struct _PDCLIB_status_t * status )
118 /* generic input character */
120 const char * orig_spec = spec;
121 if ( *(++spec) == '%' )
123 /* %% -> match single '%' */
129 if ( status->n == 0 )
141 /* Initializing status structure */
148 /* '*' suppresses assigning parsed value to variable */
151 status->flags |= E_suppressed;
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.
159 char const * prev_spec = spec;
160 status->width = (int)strtol( spec, (char**)&spec, 10 );
161 if ( spec == prev_spec )
163 status->width = SIZE_MAX;
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").
177 status->flags |= E_char;
183 status->flags |= E_short;
189 /* ll -> long long */
190 status->flags |= E_llong;
196 status->flags |= E_long;
200 /* j -> intmax_t, which might or might not be long long */
201 status->flags |= E_intmax;
204 /* z -> size_t, which might or might not be unsigned int */
205 status->flags |= E_size;
208 /* t -> ptrdiff_t, which might or might not be long */
209 status->flags |= E_ptrdiff;
212 /* L -> long double */
213 status->flags |= E_ldouble;
220 /* Conversion specifier */
222 /* whether valid input had been parsed */
223 bool value_parsed = false;
235 status->flags |= E_unsigned;
239 status->flags |= E_unsigned;
243 status->flags |= E_unsigned;
256 char * c = va_arg( status->arg, char * );
257 /* for %c, default width is one */
258 if ( status->width == SIZE_MAX )
262 /* reading until width reached or input exhausted */
263 while ( ( status->current < status->width ) &&
264 ( ( rc = GET( status ) ) != EOF ) )
269 /* width or input exhausted */
277 /* input error, no character read */
278 if ( status->n == 0 )
287 char * c = va_arg( status->arg, char * );
288 while ( ( status->current < status->width ) &&
289 ( ( rc = GET( status ) ) != EOF ) )
296 /* matching sequence terminated by whitespace */
314 /* width or input exhausted */
323 /* input error, no character read */
324 if ( status->n == 0 )
333 const char * endspec = spec;
334 bool negative_scanlist = false;
335 if ( *(++endspec) == '^' )
337 negative_scanlist = true;
343 // TODO: This can run beyond a malformed format string
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 ) )
351 if ( negative_scanlist )
353 if ( IN_SCANSET( spec, endspec, rc ) )
361 if ( ! IN_SCANSET( spec, endspec, rc ) )
387 status->flags |= E_unsigned;
391 int * val = va_arg( status->arg, int * );
396 /* No conversion specifier. Bad conversion. */
400 if ( status->base != -1 )
402 /* integer conversion */
403 uintmax_t value = 0; /* absolute value read */
404 bool prefix_parsed = false;
406 while ( ( status->current < status->width ) &&
407 ( ( rc = GET( status ) ) != EOF ) )
413 /* matching sequence terminated by whitespace */
419 /* leading whitespace not counted against width */
425 /* no sign parsed yet */
435 /* not a sign; put back character */
441 else if ( ! prefix_parsed )
443 /* no prefix (0x... for hex, 0... for octal) parsed yet */
444 prefix_parsed = true;
447 /* not a prefix; if base not yet set, set to decimal */
448 if ( status->base == 0 )
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 ) )
461 if ( tolower( rc ) == 'x' )
463 /* 0x... would be prefix for hex base... */
464 if ( ( status->base == 0 ) ||
465 ( status->base == 16 ) )
471 /* ...unless already set to other value */
478 /* 0... but not 0x.... would be octal prefix */
480 if ( status->base == 0 )
484 /* in any case we have read a zero */
490 /* failed to read beyond the initial zero */
498 char * digitptr = memchr( _PDCLIB_digits, tolower( rc ), status->base );
499 if ( digitptr == NULL )
501 /* end of input item */
505 value *= status->base;
506 value += digitptr - _PDCLIB_digits;
510 /* width or input exhausted, or non-matching character */
511 if ( ! value_parsed )
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 ) )
521 /* convert value to target type and assign to parameter */
522 if ( ! ( status->flags & E_suppressed ) )
524 switch ( status->flags & ( E_char | E_short | E_long | E_llong |
525 E_intmax | E_size | E_ptrdiff |
529 *( va_arg( status->arg, char * ) ) = (char)( value * sign );
531 case E_char | E_unsigned:
532 *( va_arg( status->arg, unsigned char * ) ) = (unsigned char)( value * sign );
536 *( va_arg( status->arg, short * ) ) = (short)( value * sign );
538 case E_short | E_unsigned:
539 *( va_arg( status->arg, unsigned short * ) ) = (unsigned short)( value * sign );
543 *( va_arg( status->arg, int * ) ) = (int)( value * sign );
546 *( va_arg( status->arg, unsigned int * ) ) = (unsigned int)( value * sign );
550 *( va_arg( status->arg, long * ) ) = (long)( value * sign );
552 case E_long | E_unsigned:
553 *( va_arg( status->arg, unsigned long * ) ) = (unsigned long)( value * sign );
557 *( va_arg( status->arg, long long * ) ) = (long long)( value * sign );
559 case E_llong | E_unsigned:
560 *( va_arg( status->arg, unsigned long long * ) ) = (unsigned long long)( value * sign );
564 *( va_arg( status->arg, intmax_t * ) ) = (intmax_t)( value * sign );
566 case E_intmax | E_unsigned:
567 *( va_arg( status->arg, uintmax_t * ) ) = (uintmax_t)( value * sign );
571 /* E_size always implies unsigned */
572 *( va_arg( status->arg, size_t * ) ) = (size_t)( value * sign );
576 /* E_ptrdiff always implies signed */
577 *( va_arg( status->arg, ptrdiff_t * ) ) = (ptrdiff_t)( value * sign );
581 puts( "UNSUPPORTED SCANF FLAG COMBINATION" );
582 return NULL; /* behaviour unspecified */
594 #define _PDCLIB_FILEID "_PDCLIB/scan.c"
595 #define _PDCLIB_STRINGIO
597 #include <_PDCLIB_test.h>
599 static int testscanf( char const * s, char const * format, ... )
601 struct _PDCLIB_status_t status;
604 status.s = (char *)s;
605 status.stream = NULL;
606 va_start( status.arg, format );
607 if ( *(_PDCLIB_scan( format, &status )) != '\0' )
609 printf( "_PDCLIB_scan() did not return end-of-specifier on '%s'.\n", format );
612 va_end( status.arg );
616 #define TEST_CONVERSION_ONLY
621 #include "scanf_testcases.h"