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 macro for assigning a readily converted integer value to the correct
36 parameter type, used in a switch on status->flags (see E_* flags above).
37 case_cond: combination of the E_* flags above, used for the switch-case
38 type: integer type, used to get the correct type from the parameter
39 stack as well as for cast target.
41 #define ASSIGN_VALUE_TO( case_cond, type ) \
43 *( va_arg( status->arg, type * ) ) = (type)( value * sign ); \
47 /* Helper function to get a character from the string or stream, whatever is
48 used for input. When reading from a string, returns EOF on end-of-string
49 so that handling of the return value can be uniform for both streams and
52 static int GET( struct _PDCLIB_status_t * status )
55 if ( status->stream != NULL )
57 rc = getc( status->stream );
61 rc = ( *status->s == '\0' ) ? EOF : (unsigned char)*((status->s)++);
72 /* Helper function to put a read character back into the string or stream,
73 whatever is used for input.
75 static void UNGET( int c, struct _PDCLIB_status_t * status )
77 if ( status->stream != NULL )
79 ungetc( c, status->stream ); /* TODO: Error? */
90 /* Helper function to check if a character is part of a given scanset */
91 static bool IN_SCANSET( const char * scanlist, const char * end_scanlist, int rc )
95 while ( scanlist != end_scanlist )
97 if ( ( *scanlist == '-' ) && ( previous != -1 ) )
99 /* possible scangroup ("a-z") */
100 if ( ++scanlist == end_scanlist )
102 /* '-' at end of scanlist does not describe a scangroup */
105 while ( ++previous <= (unsigned char)*scanlist )
107 if ( previous == rc )
116 /* not a scangroup, check verbatim */
117 if ( rc == (unsigned char)*scanlist )
121 previous = (unsigned char)(*scanlist++);
128 const char * _PDCLIB_scan( const char * spec, struct _PDCLIB_status_t * status )
130 /* generic input character */
132 const char * orig_spec = spec;
133 if ( *(++spec) == '%' )
135 /* %% -> match single '%' */
141 if ( status->n == 0 )
153 /* Initializing status structure */
160 /* '*' suppresses assigning parsed value to variable */
163 status->flags |= E_suppressed;
167 /* If a width is given, strtol() will return its value. If not given,
168 strtol() will return zero. In both cases, endptr will point to the
169 rest of the conversion specifier - just what we need.
171 char const * prev_spec = spec;
172 status->width = (int)strtol( spec, (char**)&spec, 10 );
173 if ( spec == prev_spec )
175 status->width = SIZE_MAX;
178 /* Optional length modifier
179 We step one character ahead in any case, and step back only if we find
180 there has been no length modifier (or step ahead another character if it
181 has been "hh" or "ll").
189 status->flags |= E_char;
195 status->flags |= E_short;
201 /* ll -> long long */
202 status->flags |= E_llong;
208 status->flags |= E_long;
212 /* j -> intmax_t, which might or might not be long long */
213 status->flags |= E_intmax;
216 /* z -> size_t, which might or might not be unsigned int */
217 status->flags |= E_size;
220 /* t -> ptrdiff_t, which might or might not be long */
221 status->flags |= E_ptrdiff;
224 /* L -> long double */
225 status->flags |= E_ldouble;
232 /* Conversion specifier */
234 /* whether valid input had been parsed */
235 bool value_parsed = false;
247 status->flags |= E_unsigned;
251 status->flags |= E_unsigned;
255 status->flags |= E_unsigned;
268 char * c = va_arg( status->arg, char * );
269 /* for %c, default width is one */
270 if ( status->width == SIZE_MAX )
274 /* reading until width reached or input exhausted */
275 while ( ( status->current < status->width ) &&
276 ( ( rc = GET( status ) ) != EOF ) )
281 /* width or input exhausted */
289 /* input error, no character read */
290 if ( status->n == 0 )
299 char * c = va_arg( status->arg, char * );
300 while ( ( status->current < status->width ) &&
301 ( ( rc = GET( status ) ) != EOF ) )
308 /* matching sequence terminated by whitespace */
326 /* width or input exhausted */
335 /* input error, no character read */
336 if ( status->n == 0 )
345 const char * endspec = spec;
346 bool negative_scanlist = false;
347 if ( *(++endspec) == '^' )
349 negative_scanlist = true;
355 // TODO: This can run beyond a malformed format string
357 } while ( *endspec != ']' );
358 // read according to scanlist, equiv. to %s above
359 char * c = va_arg( status->arg, char * );
360 while ( ( status->current < status->width ) &&
361 ( ( rc = GET( status ) ) != EOF ) )
363 if ( negative_scanlist )
365 if ( IN_SCANSET( spec, endspec, rc ) )
373 if ( ! IN_SCANSET( spec, endspec, rc ) )
399 status->flags |= E_unsigned;
403 int * val = va_arg( status->arg, int * );
408 /* No conversion specifier. Bad conversion. */
412 if ( status->base != -1 )
414 /* integer conversion */
415 uintmax_t value = 0; /* absolute value read */
416 bool prefix_parsed = false;
418 while ( ( status->current < status->width ) &&
419 ( ( rc = GET( status ) ) != EOF ) )
425 /* matching sequence terminated by whitespace */
431 /* leading whitespace not counted against width */
437 /* no sign parsed yet */
447 /* not a sign; put back character */
453 else if ( ! prefix_parsed )
455 /* no prefix (0x... for hex, 0... for octal) parsed yet */
456 prefix_parsed = true;
459 /* not a prefix; if base not yet set, set to decimal */
460 if ( status->base == 0 )
468 /* starts with zero, so it might be a prefix. */
469 /* check what follows next (might be 0x...) */
470 if ( ( status->current < status->width ) &&
471 ( ( rc = GET( status ) ) != EOF ) )
473 if ( tolower( rc ) == 'x' )
475 /* 0x... would be prefix for hex base... */
476 if ( ( status->base == 0 ) ||
477 ( status->base == 16 ) )
483 /* ...unless already set to other value */
490 /* 0... but not 0x.... would be octal prefix */
492 if ( status->base == 0 )
496 /* in any case we have read a zero */
502 /* failed to read beyond the initial zero */
510 char * digitptr = memchr( _PDCLIB_digits, tolower( rc ), status->base );
511 if ( digitptr == NULL )
513 /* end of input item */
517 value *= status->base;
518 value += digitptr - _PDCLIB_digits;
522 /* width or input exhausted, or non-matching character */
523 if ( ! value_parsed )
525 /* out of input before anything could be parsed - input error */
526 /* FIXME: if first character does not match, value_parsed is not set - but it is NOT an input error */
527 if ( ( status->n == 0 ) && ( rc == EOF ) )
533 /* convert value to target type and assign to parameter */
534 switch ( status->flags & ( E_char | E_short | E_long | E_llong |
535 E_intmax | E_size | E_ptrdiff |
538 ASSIGN_VALUE_TO( E_char, char );
539 ASSIGN_VALUE_TO( E_char | E_unsigned, unsigned char );
540 ASSIGN_VALUE_TO( E_short, short );
541 ASSIGN_VALUE_TO( E_short | E_unsigned, unsigned short );
542 ASSIGN_VALUE_TO( 0, int );
543 ASSIGN_VALUE_TO( E_unsigned, unsigned int );
544 ASSIGN_VALUE_TO( E_long, long );
545 ASSIGN_VALUE_TO( E_long | E_unsigned, unsigned long );
546 ASSIGN_VALUE_TO( E_llong, long long );
547 ASSIGN_VALUE_TO( E_llong | E_unsigned, unsigned long long );
548 ASSIGN_VALUE_TO( E_intmax, intmax_t );
549 ASSIGN_VALUE_TO( E_intmax | E_unsigned, uintmax_t );
550 ASSIGN_VALUE_TO( E_size, size_t );
551 /* ASSIGN_VALUE_TO( E_size | E_unsigned, unsigned size_t ); */
552 ASSIGN_VALUE_TO( E_ptrdiff, ptrdiff_t );
553 /* ASSIGN_VALUE_TO( E_ptrdiff | E_unsigned, unsigned ptrdiff_t ); */
555 puts( "UNSUPPORTED SCANF FLAG COMBINATION" );
556 return NULL; /* behaviour unspecified */
567 #include <_PDCLIB_test.h>
574 /* Testing covered by fscanf.c */