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.
18 /* Using an integer's bits as flags for both the conversion flags and length
21 #define E_suppressed 1<<0
26 #define E_intmax 1<<10
28 #define E_ptrdiff 1<<12
29 #define E_intptr 1<<13
30 #define E_ldouble 1<<14
31 #define E_unsigned 1<<16
34 /* Helper macro for assigning a readily converted integer value to the correct
35 parameter type, used in a switch on status->flags (see E_* flags above).
36 case_cond: combination of the E_* flags above, used for the switch-case
37 type: integer type, used to get the correct type from the parameter
38 stack as well as for cast target.
40 #define ASSIGN( case_cond, type ) \
42 *( va_arg( status->arg, type * ) ) = (type)( value * sign ); \
46 /* Helper function to get a character from the string or stream, whatever is
47 used for input. When reading from a string, returns EOF on end-of-string
48 so that handling of the return value can be uniform for both streams and
51 static int GET( struct _PDCLIB_status_t * status )
54 if ( status->stream != NULL )
56 rc = getc( status->stream );
60 rc = ( *status->s == '\0' ) ? EOF : (unsigned char)*((status->s)++);
71 /* Helper function to put a read character back into the string or stream,
72 whatever is used for input.
74 static void UNGET( int c, struct _PDCLIB_status_t * status )
76 if ( status->stream != NULL )
78 ungetc( c, status->stream ); /* TODO: Error? */
89 const char * _PDCLIB_scan( const char * spec, struct _PDCLIB_status_t * status )
91 /* generic input character */
93 const char * orig_spec = spec;
94 if ( *(++spec) == '%' )
96 /* %% -> match single '%' */
102 if ( status->n == 0 )
114 /* Initializing status structure */
121 /* '*' suppresses assigning parsed value to variable */
124 status->flags |= E_suppressed;
128 /* If a width is given, strtol() will return its value. If not given,
129 strtol() will return zero. In both cases, endptr will point to the
130 rest of the conversion specifier - just what we need.
132 char const * prev_spec = spec;
133 status->width = (int)strtol( spec, (char**)&spec, 10 );
134 if ( spec == prev_spec )
136 status->width = SIZE_MAX;
139 /* Optional length modifier
140 We step one character ahead in any case, and step back only if we find
141 there has been no length modifier (or step ahead another character if it
142 has been "hh" or "ll").
150 status->flags |= E_char;
156 status->flags |= E_short;
162 /* ll -> long long */
163 status->flags |= E_llong;
169 status->flags |= E_long;
173 /* j -> intmax_t, which might or might not be long long */
174 status->flags |= E_intmax;
177 /* z -> size_t, which might or might not be unsigned int */
178 status->flags |= E_size;
181 /* t -> ptrdiff_t, which might or might not be long */
182 status->flags |= E_ptrdiff;
185 /* L -> long double */
186 status->flags |= E_ldouble;
193 /* Conversion specifier */
195 /* whether valid input had been parsed */
196 bool value_parsed = false;
208 status->flags |= E_unsigned;
212 status->flags |= E_unsigned;
216 status->flags |= E_unsigned;
229 char * c = va_arg( status->arg, char * );
230 /* for %c, default width is one */
231 if ( status->width == SIZE_MAX )
235 /* reading until width reached or input exhausted */
236 while ( ( status->this < status->width ) &&
237 ( ( rc = GET( status ) ) != EOF ) )
242 /* width or input exhausted */
250 /* input error, no character read */
251 if ( status->n == 0 )
260 char * c = va_arg( status->arg, char * );
261 while ( ( status->this < status->width ) &&
262 ( ( rc = GET( status ) ) != EOF ) )
268 /* matching sequence terminated by whitespace */
274 /* leading whitespace not counted against width */
284 /* width or input exhausted */
293 /* input error, no character read */
294 if ( status->n == 0 )
303 status->flags |= E_unsigned;
307 int * val = va_arg( status->arg, int * );
312 /* No conversion specifier. Bad conversion. */
316 if ( status->base != -1 )
318 /* integer conversion */
320 bool prefix_parsed = false;
322 while ( ( status->this < status->width ) &&
323 ( ( rc = GET( status ) ) != EOF ) )
329 /* matching sequence terminated by whitespace */
335 /* leading whitespace not counted against width */
341 /* no sign parsed yet */
351 /* not a sign; put back character */
357 else if ( ! prefix_parsed )
359 /* no prefix (0x... for hex, 0... for octal) parsed yet */
360 prefix_parsed = true;
363 /* not a prefix; if base not yet set, set to decimal */
364 if ( status->base == 0 )
372 /* starts with zero, so it might be a prefix. */
373 /* check what follows next (might be 0x...) */
374 if ( ( status->this < status->width ) &&
375 ( ( rc = GET( status ) ) != EOF ) )
377 if ( tolower( rc ) == 'x' )
379 /* 0x... would be prefix for hex base... */
380 if ( ( status->base == 0 ) ||
381 ( status->base == 16 ) )
387 /* ...unless already set to other value */
394 /* 0... but not 0x.... would be octal prefix */
396 if ( status->base == 0 )
400 /* in any case we have read a zero */
406 /* failed to read beyond the initial zero */
414 char * digitptr = memchr( _PDCLIB_digits, tolower( rc ), status->base );
415 if ( digitptr == NULL )
417 /* end of input item */
421 value *= status->base;
422 value += digitptr - _PDCLIB_digits;
426 /* width or input exhausted, or non-matching character */
427 if ( ! value_parsed )
429 /* out of input before anything could be parsed - input error */
430 /* FIXME: if first character does not match, value_parsed is not set - but it is NOT an input error */
431 if ( status->n == 0 )
437 /* convert value to target type and assign to parameter */
438 switch ( status->flags & ( E_char | E_short | E_long | E_llong |
439 E_intmax | E_size | E_ptrdiff |
442 ASSIGN( E_char, char );
443 ASSIGN( E_char | E_unsigned, unsigned char );
444 ASSIGN( E_short, short );
445 ASSIGN( E_short | E_unsigned, unsigned short );
447 ASSIGN( E_unsigned, unsigned int );
448 ASSIGN( E_long, long );
449 ASSIGN( E_long | E_unsigned, unsigned long );
450 ASSIGN( E_llong, long long );
451 ASSIGN( E_llong | E_unsigned, unsigned long long );
452 ASSIGN( E_intmax, intmax_t );
453 ASSIGN( E_intmax | E_unsigned, uintmax_t );
454 ASSIGN( E_size, size_t );
455 /* ASSIGN( E_size | E_unsigned, unsigned size_t ); */
456 ASSIGN( E_ptrdiff, ptrdiff_t );
457 /* ASSIGN( E_ptrdiff | E_unsigned, unsigned ptrdiff_t ); */
459 puts( "UNSUPPORTED SCANF FLAG COMBINATION" );
460 return NULL; /* behaviour unspecified */
471 #include <_PDCLIB_test.h>
478 /* Testing covered by fscanf.c */