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.
21 /* Using an integer's bits as flags for both the conversion flags and length
24 #define E_suppressed 1<<0
29 #define E_intmax 1<<10
31 #define E_ptrdiff 1<<12
32 #define E_intptr 1<<13
33 #define E_ldouble 1<<14
34 #define E_unsigned 1<<16
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
42 static int GET( struct _PDCLIB_status_t * status )
45 if ( status->stream != NULL )
47 rc = getc( status->stream );
51 rc = ( *status->s == '\0' ) ? EOF : (unsigned char)*((status->s)++);
62 /* Helper function to put a read character back into the string or stream,
63 whatever is used for input.
65 static void UNGET( int c, struct _PDCLIB_status_t * status )
67 if ( status->stream != NULL )
69 ungetc( c, status->stream ); /* TODO: Error? */
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 )
85 while ( scanlist != end_scanlist )
87 if ( ( *scanlist == '-' ) && ( previous != -1 ) )
89 /* possible scangroup ("a-z") */
90 if ( ++scanlist == end_scanlist )
92 /* '-' at end of scanlist does not describe a scangroup */
95 while ( ++previous <= (unsigned char)*scanlist )
106 /* not a scangroup, check verbatim */
107 if ( rc == (unsigned char)*scanlist )
111 previous = (unsigned char)(*scanlist++);
118 const char * _PDCLIB_scan( const char * spec, struct _PDCLIB_status_t * status )
120 /* generic input character */
122 const char * orig_spec = spec;
123 if ( *(++spec) == '%' )
125 /* %% -> match single '%' */
131 if ( status->n == 0 )
143 /* Initializing status structure */
150 /* '*' suppresses assigning parsed value to variable */
153 status->flags |= E_suppressed;
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.
161 char const * prev_spec = spec;
162 status->width = (int)strtol( spec, (char**)&spec, 10 );
163 if ( spec == prev_spec )
165 status->width = SIZE_MAX;
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").
179 status->flags |= E_char;
185 status->flags |= E_short;
191 /* ll -> long long */
192 status->flags |= E_llong;
198 status->flags |= E_long;
202 /* j -> intmax_t, which might or might not be long long */
203 status->flags |= E_intmax;
206 /* z -> size_t, which might or might not be unsigned int */
207 status->flags |= E_size;
210 /* t -> ptrdiff_t, which might or might not be long */
211 status->flags |= E_ptrdiff;
214 /* L -> long double */
215 status->flags |= E_ldouble;
222 /* Conversion specifier */
224 /* whether valid input had been parsed */
225 bool value_parsed = false;
237 status->flags |= E_unsigned;
241 status->flags |= E_unsigned;
245 status->flags |= E_unsigned;
258 char * c = va_arg( status->arg, char * );
259 /* for %c, default width is one */
260 if ( status->width == SIZE_MAX )
264 /* reading until width reached or input exhausted */
265 while ( ( status->current < status->width ) &&
266 ( ( rc = GET( status ) ) != EOF ) )
271 /* width or input exhausted */
279 /* input error, no character read */
280 if ( status->n == 0 )
289 char * c = va_arg( status->arg, char * );
290 while ( ( status->current < status->width ) &&
291 ( ( rc = GET( status ) ) != EOF ) )
298 /* matching sequence terminated by whitespace */
316 /* width or input exhausted */
325 /* input error, no character read */
326 if ( status->n == 0 )
335 const char * endspec = spec;
336 bool negative_scanlist = false;
337 if ( *(++endspec) == '^' )
339 negative_scanlist = true;
345 // TODO: This can run beyond a malformed format string
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 ) )
353 if ( negative_scanlist )
355 if ( IN_SCANSET( spec, endspec, rc ) )
363 if ( ! IN_SCANSET( spec, endspec, rc ) )
389 status->flags |= E_unsigned;
393 int * val = va_arg( status->arg, int * );
398 /* No conversion specifier. Bad conversion. */
402 if ( status->base != -1 )
404 /* integer conversion */
405 uintmax_t value = 0; /* absolute value read */
406 bool prefix_parsed = false;
408 while ( ( status->current < status->width ) &&
409 ( ( rc = GET( status ) ) != EOF ) )
415 /* matching sequence terminated by whitespace */
421 /* leading whitespace not counted against width */
427 /* no sign parsed yet */
437 /* not a sign; put back character */
443 else if ( ! prefix_parsed )
445 /* no prefix (0x... for hex, 0... for octal) parsed yet */
446 prefix_parsed = true;
449 /* not a prefix; if base not yet set, set to decimal */
450 if ( status->base == 0 )
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 ) )
463 if ( tolower( rc ) == 'x' )
465 /* 0x... would be prefix for hex base... */
466 if ( ( status->base == 0 ) ||
467 ( status->base == 16 ) )
473 /* ...unless already set to other value */
480 /* 0... but not 0x.... would be octal prefix */
482 if ( status->base == 0 )
486 /* in any case we have read a zero */
492 /* failed to read beyond the initial zero */
500 char * digitptr = memchr( _PDCLIB_digits, tolower( rc ), status->base );
501 if ( digitptr == NULL )
503 /* end of input item */
507 value *= status->base;
508 value += digitptr - _PDCLIB_digits;
512 /* width or input exhausted, or non-matching character */
513 if ( ! value_parsed )
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 ) )
523 /* convert value to target type and assign to parameter */
524 if ( ! ( status->flags & E_suppressed ) )
526 switch ( status->flags & ( E_char | E_short | E_long | E_llong |
527 E_intmax | E_size | E_ptrdiff |
531 *( va_arg( status->arg, char * ) ) = (char)( value * sign );
533 case E_char | E_unsigned:
534 *( va_arg( status->arg, unsigned char * ) ) = (unsigned char)( value * sign );
538 *( va_arg( status->arg, short * ) ) = (short)( value * sign );
540 case E_short | E_unsigned:
541 *( va_arg( status->arg, unsigned short * ) ) = (unsigned short)( value * sign );
545 *( va_arg( status->arg, int * ) ) = (int)( value * sign );
548 *( va_arg( status->arg, unsigned int * ) ) = (unsigned int)( value * sign );
552 *( va_arg( status->arg, long * ) ) = (long)( value * sign );
554 case E_long | E_unsigned:
555 *( va_arg( status->arg, unsigned long * ) ) = (unsigned long)( value * sign );
559 *( va_arg( status->arg, long long * ) ) = (long long)( value * sign );
561 case E_llong | E_unsigned:
562 *( va_arg( status->arg, unsigned long long * ) ) = (unsigned long long)( value * sign );
566 *( va_arg( status->arg, intmax_t * ) ) = (intmax_t)( value * sign );
568 case E_intmax | E_unsigned:
569 *( va_arg( status->arg, uintmax_t * ) ) = (uintmax_t)( value * sign );
573 /* E_size always implies unsigned */
574 *( va_arg( status->arg, size_t * ) ) = (size_t)( value * sign );
578 /* E_ptrdiff always implies signed */
579 *( va_arg( status->arg, ptrdiff_t * ) ) = (ptrdiff_t)( value * sign );
583 puts( "UNSUPPORTED SCANF FLAG COMBINATION" );
584 return NULL; /* behaviour unspecified */
596 #define _PDCLIB_FILEID "_PDCLIB/scan.c"
597 #define _PDCLIB_STRINGIO
599 #include <_PDCLIB_test.h>
602 static int testscanf( char const * s, char const * format, ... )
604 struct _PDCLIB_status_t status;
607 status.s = (char *)s;
608 status.stream = NULL;
609 va_start( status.arg, format );
610 if ( *(_PDCLIB_scan( format, &status )) != '\0' )
612 printf( "_PDCLIB_scan() did not return end-of-specifier on '%s'.\n", format );
615 va_end( status.arg );
620 #define TEST_CONVERSION_ONLY
626 #include "scanf_testcases.h"