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 const char * _PDCLIB_scan( const char * spec, struct _PDCLIB_status_t * status )
92 /* generic input character */
94 const char * orig_spec = spec;
95 if ( *(++spec) == '%' )
97 /* %% -> match single '%' */
103 if ( status->n == 0 )
115 /* Initializing status structure */
122 /* '*' suppresses assigning parsed value to variable */
125 status->flags |= E_suppressed;
129 /* If a width is given, strtol() will return its value. If not given,
130 strtol() will return zero. In both cases, endptr will point to the
131 rest of the conversion specifier - just what we need.
133 char const * prev_spec = spec;
134 status->width = (int)strtol( spec, (char**)&spec, 10 );
135 if ( spec == prev_spec )
137 status->width = SIZE_MAX;
140 /* Optional length modifier
141 We step one character ahead in any case, and step back only if we find
142 there has been no length modifier (or step ahead another character if it
143 has been "hh" or "ll").
151 status->flags |= E_char;
157 status->flags |= E_short;
163 /* ll -> long long */
164 status->flags |= E_llong;
170 status->flags |= E_long;
174 /* j -> intmax_t, which might or might not be long long */
175 status->flags |= E_intmax;
178 /* z -> size_t, which might or might not be unsigned int */
179 status->flags |= E_size;
182 /* t -> ptrdiff_t, which might or might not be long */
183 status->flags |= E_ptrdiff;
186 /* L -> long double */
187 status->flags |= E_ldouble;
194 /* Conversion specifier */
196 /* whether valid input had been parsed */
197 bool value_parsed = false;
209 status->flags |= E_unsigned;
213 status->flags |= E_unsigned;
217 status->flags |= E_unsigned;
230 char * c = va_arg( status->arg, char * );
231 /* for %c, default width is one */
232 if ( status->width == SIZE_MAX )
236 /* reading until width reached or input exhausted */
237 while ( ( status->this < status->width ) &&
238 ( ( rc = GET( status ) ) != EOF ) )
243 /* width or input exhausted */
251 /* input error, no character read */
252 if ( status->n == 0 )
261 char * c = va_arg( status->arg, char * );
262 while ( ( status->this < status->width ) &&
263 ( ( rc = GET( status ) ) != EOF ) )
269 /* matching sequence terminated by whitespace */
275 /* leading whitespace not counted against width */
285 /* width or input exhausted */
294 /* input error, no character read */
295 if ( status->n == 0 )
307 status->flags |= E_unsigned;
311 int * val = va_arg( status->arg, int * );
316 /* No conversion specifier. Bad conversion. */
320 if ( status->base != -1 )
322 /* integer conversion */
323 uintmax_t value = 0; /* absolute value read */
324 bool prefix_parsed = false;
326 while ( ( status->this < status->width ) &&
327 ( ( rc = GET( status ) ) != EOF ) )
333 /* matching sequence terminated by whitespace */
339 /* leading whitespace not counted against width */
345 /* no sign parsed yet */
355 /* not a sign; put back character */
361 else if ( ! prefix_parsed )
363 /* no prefix (0x... for hex, 0... for octal) parsed yet */
364 prefix_parsed = true;
367 /* not a prefix; if base not yet set, set to decimal */
368 if ( status->base == 0 )
376 /* starts with zero, so it might be a prefix. */
377 /* check what follows next (might be 0x...) */
378 if ( ( status->this < status->width ) &&
379 ( ( rc = GET( status ) ) != EOF ) )
381 if ( tolower( rc ) == 'x' )
383 /* 0x... would be prefix for hex base... */
384 if ( ( status->base == 0 ) ||
385 ( status->base == 16 ) )
391 /* ...unless already set to other value */
398 /* 0... but not 0x.... would be octal prefix */
400 if ( status->base == 0 )
404 /* in any case we have read a zero */
410 /* failed to read beyond the initial zero */
418 char * digitptr = memchr( _PDCLIB_digits, tolower( rc ), status->base );
419 if ( digitptr == NULL )
421 /* end of input item */
425 value *= status->base;
426 value += digitptr - _PDCLIB_digits;
430 /* width or input exhausted, or non-matching character */
431 if ( ! value_parsed )
433 /* out of input before anything could be parsed - input error */
434 /* FIXME: if first character does not match, value_parsed is not set - but it is NOT an input error */
435 if ( ( status->n == 0 ) && ( rc == EOF ) )
441 /* convert value to target type and assign to parameter */
442 switch ( status->flags & ( E_char | E_short | E_long | E_llong |
443 E_intmax | E_size | E_ptrdiff |
446 ASSIGN_VALUE_TO( E_char, char );
447 ASSIGN_VALUE_TO( E_char | E_unsigned, unsigned char );
448 ASSIGN_VALUE_TO( E_short, short );
449 ASSIGN_VALUE_TO( E_short | E_unsigned, unsigned short );
450 ASSIGN_VALUE_TO( 0, int );
451 ASSIGN_VALUE_TO( E_unsigned, unsigned int );
452 ASSIGN_VALUE_TO( E_long, long );
453 ASSIGN_VALUE_TO( E_long | E_unsigned, unsigned long );
454 ASSIGN_VALUE_TO( E_llong, long long );
455 ASSIGN_VALUE_TO( E_llong | E_unsigned, unsigned long long );
456 ASSIGN_VALUE_TO( E_intmax, intmax_t );
457 ASSIGN_VALUE_TO( E_intmax | E_unsigned, uintmax_t );
458 ASSIGN_VALUE_TO( E_size, size_t );
459 /* ASSIGN_VALUE_TO( E_size | E_unsigned, unsigned size_t ); */
460 ASSIGN_VALUE_TO( E_ptrdiff, ptrdiff_t );
461 /* ASSIGN_VALUE_TO( E_ptrdiff | E_unsigned, unsigned ptrdiff_t ); */
463 puts( "UNSUPPORTED SCANF FLAG COMBINATION" );
464 return NULL; /* behaviour unspecified */
475 #include <_PDCLIB_test.h>
482 /* Testing covered by fscanf.c */