1 /* _PDCLIB_scan( const char *, struct _PDCLIB_status_t * )
3 This file is part of the Public Domain C Library (PDCLib).
4 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 = UINT_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 // TODO: Like _PDCLIB_print, E_pointer(?)
388 status->flags |= E_unsigned | E_long;
392 int * val = va_arg( status->arg, int * );
397 /* No conversion specifier. Bad conversion. */
401 if ( status->base != -1 )
403 /* integer conversion */
404 uintmax_t value = 0; /* absolute value read */
405 bool prefix_parsed = false;
407 while ( ( status->current < status->width ) &&
408 ( ( rc = GET( status ) ) != EOF ) )
414 /* matching sequence terminated by whitespace */
420 /* leading whitespace not counted against width */
426 /* no sign parsed yet */
436 /* not a sign; put back character */
442 else if ( ! prefix_parsed )
444 /* no prefix (0x... for hex, 0... for octal) parsed yet */
445 prefix_parsed = true;
448 /* not a prefix; if base not yet set, set to decimal */
449 if ( status->base == 0 )
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 ) )
462 if ( tolower( rc ) == 'x' )
464 /* 0x... would be prefix for hex base... */
465 if ( ( status->base == 0 ) ||
466 ( status->base == 16 ) )
472 /* ...unless already set to other value */
479 /* 0... but not 0x.... would be octal prefix */
481 if ( status->base == 0 )
485 /* in any case we have read a zero */
491 /* failed to read beyond the initial zero */
499 char * digitptr = memchr( _PDCLIB_digits, tolower( rc ), status->base );
500 if ( digitptr == NULL )
502 /* end of input item */
506 value *= status->base;
507 value += digitptr - _PDCLIB_digits;
511 /* width or input exhausted, or non-matching character */
512 if ( ! value_parsed )
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 ) )
522 /* convert value to target type and assign to parameter */
523 if ( ! ( status->flags & E_suppressed ) )
525 switch ( status->flags & ( E_char | E_short | E_long | E_llong |
526 E_intmax | E_size | E_ptrdiff |
530 *( va_arg( status->arg, char * ) ) = (char)( value * sign );
532 case E_char | E_unsigned:
533 *( va_arg( status->arg, unsigned char * ) ) = (unsigned char)( value * sign );
537 *( va_arg( status->arg, short * ) ) = (short)( value * sign );
539 case E_short | E_unsigned:
540 *( va_arg( status->arg, unsigned short * ) ) = (unsigned short)( value * sign );
544 *( va_arg( status->arg, int * ) ) = (int)( value * sign );
547 *( va_arg( status->arg, unsigned int * ) ) = (unsigned int)( value * sign );
551 *( va_arg( status->arg, long * ) ) = (long)( value * sign );
553 case E_long | E_unsigned:
554 *( va_arg( status->arg, unsigned long * ) ) = (unsigned long)( value * sign );
558 *( va_arg( status->arg, long long * ) ) = (long long)( value * sign );
560 case E_llong | E_unsigned:
561 *( va_arg( status->arg, unsigned long long * ) ) = (unsigned long long)( value * sign );
565 *( va_arg( status->arg, intmax_t * ) ) = (intmax_t)( value * sign );
567 case E_intmax | E_unsigned:
568 *( va_arg( status->arg, uintmax_t * ) ) = (uintmax_t)( value * sign );
572 /* E_size always implies unsigned */
573 *( va_arg( status->arg, size_t * ) ) = (size_t)( value * sign );
577 /* E_ptrdiff always implies signed */
578 *( va_arg( status->arg, ptrdiff_t * ) ) = (ptrdiff_t)( value * sign );
582 puts( "UNSUPPORTED SCANF FLAG COMBINATION" );
583 return NULL; /* behaviour unspecified */
595 #define _PDCLIB_FILEID "_PDCLIB/scan.c"
596 #define _PDCLIB_STRINGIO
598 #include <_PDCLIB_test.h>
601 static int testscanf( char const * s, char const * format, ... )
603 struct _PDCLIB_status_t status;
606 status.s = (char *)s;
607 status.stream = NULL;
608 va_start( status.arg, format );
609 if ( *(_PDCLIB_scan( format, &status )) != '\0' )
611 printf( "_PDCLIB_scan() did not return end-of-specifier on '%s'.\n", format );
614 va_end( status.arg );
619 #define TEST_CONVERSION_ONLY
625 #include "scanf_testcases.h"