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 NOT_IN_SCANSET( const char * start_scanlist, const char * end_scanlist, bool negate_scanlist, int rc )
98 const char * _PDCLIB_scan( const char * spec, struct _PDCLIB_status_t * status )
100 /* generic input character */
102 const char * orig_spec = spec;
103 if ( *(++spec) == '%' )
105 /* %% -> match single '%' */
111 if ( status->n == 0 )
123 /* Initializing status structure */
130 /* '*' suppresses assigning parsed value to variable */
133 status->flags |= E_suppressed;
137 /* If a width is given, strtol() will return its value. If not given,
138 strtol() will return zero. In both cases, endptr will point to the
139 rest of the conversion specifier - just what we need.
141 char const * prev_spec = spec;
142 status->width = (int)strtol( spec, (char**)&spec, 10 );
143 if ( spec == prev_spec )
145 status->width = SIZE_MAX;
148 /* Optional length modifier
149 We step one character ahead in any case, and step back only if we find
150 there has been no length modifier (or step ahead another character if it
151 has been "hh" or "ll").
159 status->flags |= E_char;
165 status->flags |= E_short;
171 /* ll -> long long */
172 status->flags |= E_llong;
178 status->flags |= E_long;
182 /* j -> intmax_t, which might or might not be long long */
183 status->flags |= E_intmax;
186 /* z -> size_t, which might or might not be unsigned int */
187 status->flags |= E_size;
190 /* t -> ptrdiff_t, which might or might not be long */
191 status->flags |= E_ptrdiff;
194 /* L -> long double */
195 status->flags |= E_ldouble;
202 /* Conversion specifier */
204 /* whether valid input had been parsed */
205 bool value_parsed = false;
217 status->flags |= E_unsigned;
221 status->flags |= E_unsigned;
225 status->flags |= E_unsigned;
238 char * c = va_arg( status->arg, char * );
239 /* for %c, default width is one */
240 if ( status->width == SIZE_MAX )
244 /* reading until width reached or input exhausted */
245 while ( ( status->this < status->width ) &&
246 ( ( rc = GET( status ) ) != EOF ) )
251 /* width or input exhausted */
259 /* input error, no character read */
260 if ( status->n == 0 )
269 char * c = va_arg( status->arg, char * );
270 while ( ( status->this < status->width ) &&
271 ( ( rc = GET( status ) ) != EOF ) )
278 /* matching sequence terminated by whitespace */
296 /* width or input exhausted */
305 /* input error, no character read */
306 if ( status->n == 0 )
315 const char * endspec = spec;
316 bool negative_scanlist = false;
317 if ( *(++endspec) == '^' )
319 negative_scanlist = true;
325 // TODO: This can run beyond a malformed format string
327 } while ( *endspec != ']' );
328 // read according to scanlist, equiv. to %s above
329 char * c = va_arg( status->arg, char * );
330 while ( ( status->this < status->width ) &&
331 ( ( rc = GET( status ) ) != EOF ) )
333 if ( NOT_IN_SCANSET( spec, endspec, negative_scanlist, rc ) )
348 if ( status->n == 0 )
357 status->flags |= E_unsigned;
361 int * val = va_arg( status->arg, int * );
366 /* No conversion specifier. Bad conversion. */
370 if ( status->base != -1 )
372 /* integer conversion */
373 uintmax_t value = 0; /* absolute value read */
374 bool prefix_parsed = false;
376 while ( ( status->this < status->width ) &&
377 ( ( rc = GET( status ) ) != EOF ) )
383 /* matching sequence terminated by whitespace */
389 /* leading whitespace not counted against width */
395 /* no sign parsed yet */
405 /* not a sign; put back character */
411 else if ( ! prefix_parsed )
413 /* no prefix (0x... for hex, 0... for octal) parsed yet */
414 prefix_parsed = true;
417 /* not a prefix; if base not yet set, set to decimal */
418 if ( status->base == 0 )
426 /* starts with zero, so it might be a prefix. */
427 /* check what follows next (might be 0x...) */
428 if ( ( status->this < status->width ) &&
429 ( ( rc = GET( status ) ) != EOF ) )
431 if ( tolower( rc ) == 'x' )
433 /* 0x... would be prefix for hex base... */
434 if ( ( status->base == 0 ) ||
435 ( status->base == 16 ) )
441 /* ...unless already set to other value */
448 /* 0... but not 0x.... would be octal prefix */
450 if ( status->base == 0 )
454 /* in any case we have read a zero */
460 /* failed to read beyond the initial zero */
468 char * digitptr = memchr( _PDCLIB_digits, tolower( rc ), status->base );
469 if ( digitptr == NULL )
471 /* end of input item */
475 value *= status->base;
476 value += digitptr - _PDCLIB_digits;
480 /* width or input exhausted, or non-matching character */
481 if ( ! value_parsed )
483 /* out of input before anything could be parsed - input error */
484 /* FIXME: if first character does not match, value_parsed is not set - but it is NOT an input error */
485 if ( ( status->n == 0 ) && ( rc == EOF ) )
491 /* convert value to target type and assign to parameter */
492 switch ( status->flags & ( E_char | E_short | E_long | E_llong |
493 E_intmax | E_size | E_ptrdiff |
496 ASSIGN_VALUE_TO( E_char, char );
497 ASSIGN_VALUE_TO( E_char | E_unsigned, unsigned char );
498 ASSIGN_VALUE_TO( E_short, short );
499 ASSIGN_VALUE_TO( E_short | E_unsigned, unsigned short );
500 ASSIGN_VALUE_TO( 0, int );
501 ASSIGN_VALUE_TO( E_unsigned, unsigned int );
502 ASSIGN_VALUE_TO( E_long, long );
503 ASSIGN_VALUE_TO( E_long | E_unsigned, unsigned long );
504 ASSIGN_VALUE_TO( E_llong, long long );
505 ASSIGN_VALUE_TO( E_llong | E_unsigned, unsigned long long );
506 ASSIGN_VALUE_TO( E_intmax, intmax_t );
507 ASSIGN_VALUE_TO( E_intmax | E_unsigned, uintmax_t );
508 ASSIGN_VALUE_TO( E_size, size_t );
509 /* ASSIGN_VALUE_TO( E_size | E_unsigned, unsigned size_t ); */
510 ASSIGN_VALUE_TO( E_ptrdiff, ptrdiff_t );
511 /* ASSIGN_VALUE_TO( E_ptrdiff | E_unsigned, unsigned ptrdiff_t ); */
513 puts( "UNSUPPORTED SCANF FLAG COMBINATION" );
514 return NULL; /* behaviour unspecified */
525 #include <_PDCLIB_test.h>
532 /* Testing covered by fscanf.c */