1 /* _PDCLIB_scan( const char *, struct _PDCLIB_status_t * )
3 This file is part of the Public Domain C Library (PDCLib).
4 Permission is granted to use, modify, and / or redistribute at will.
17 /* Using an integer's bits as flags for both the conversion flags and length
20 #define E_suppressed 1<<0
25 #define E_intmax 1<<10
27 #define E_ptrdiff 1<<12
28 #define E_intptr 1<<13
29 #define E_ldouble 1<<14
30 #define E_unsigned 1<<16
33 /* Helper function to get a character from the string or stream, whatever is
34 used for input. When reading from a string, returns EOF on end-of-string
35 so that handling of the return value can be uniform for both streams and
38 static int GET( struct _PDCLIB_status_t * status )
41 if ( status->stream != NULL )
43 rc = getc( status->stream );
47 rc = ( *status->s == '\0' ) ? EOF : (unsigned char)*((status->s)++);
58 /* Helper function to put a read character back into the string or stream,
59 whatever is used for input.
61 static void UNGET( int c, struct _PDCLIB_status_t * status )
63 if ( status->stream != NULL )
65 ungetc( c, status->stream ); /* TODO: Error? */
76 /* Helper function to check if a character is part of a given scanset */
77 static bool IN_SCANSET( const char * scanlist, const char * end_scanlist, int rc )
81 while ( scanlist != end_scanlist )
83 if ( ( *scanlist == '-' ) && ( previous != -1 ) )
85 /* possible scangroup ("a-z") */
86 if ( ++scanlist == end_scanlist )
88 /* '-' at end of scanlist does not describe a scangroup */
91 while ( ++previous <= (unsigned char)*scanlist )
102 /* not a scangroup, check verbatim */
103 if ( rc == (unsigned char)*scanlist )
107 previous = (unsigned char)(*scanlist++);
114 const char * _PDCLIB_scan( const char * spec, struct _PDCLIB_status_t * status )
116 /* generic input character */
118 const char * orig_spec = spec;
119 if ( *(++spec) == '%' )
121 /* %% -> match single '%' */
127 if ( status->n == 0 )
139 /* Initializing status structure */
146 /* '*' suppresses assigning parsed value to variable */
149 status->flags |= E_suppressed;
153 /* If a width is given, strtol() will return its value. If not given,
154 strtol() will return zero. In both cases, endptr will point to the
155 rest of the conversion specifier - just what we need.
157 char const * prev_spec = spec;
158 status->width = (int)strtol( spec, (char**)&spec, 10 );
159 if ( spec == prev_spec )
161 status->width = SIZE_MAX;
164 /* Optional length modifier
165 We step one character ahead in any case, and step back only if we find
166 there has been no length modifier (or step ahead another character if it
167 has been "hh" or "ll").
175 status->flags |= E_char;
181 status->flags |= E_short;
187 /* ll -> long long */
188 status->flags |= E_llong;
194 status->flags |= E_long;
198 /* j -> intmax_t, which might or might not be long long */
199 status->flags |= E_intmax;
202 /* z -> size_t, which might or might not be unsigned int */
203 status->flags |= E_size;
206 /* t -> ptrdiff_t, which might or might not be long */
207 status->flags |= E_ptrdiff;
210 /* L -> long double */
211 status->flags |= E_ldouble;
218 /* Conversion specifier */
220 /* whether valid input had been parsed */
221 bool value_parsed = false;
233 status->flags |= E_unsigned;
237 status->flags |= E_unsigned;
241 status->flags |= E_unsigned;
254 char * c = va_arg( status->arg, char * );
255 /* for %c, default width is one */
256 if ( status->width == SIZE_MAX )
260 /* reading until width reached or input exhausted */
261 while ( ( status->current < status->width ) &&
262 ( ( rc = GET( status ) ) != EOF ) )
267 /* width or input exhausted */
275 /* input error, no character read */
276 if ( status->n == 0 )
285 char * c = va_arg( status->arg, char * );
286 while ( ( status->current < status->width ) &&
287 ( ( rc = GET( status ) ) != EOF ) )
294 /* matching sequence terminated by whitespace */
312 /* width or input exhausted */
321 /* input error, no character read */
322 if ( status->n == 0 )
331 const char * endspec = spec;
332 bool negative_scanlist = false;
333 if ( *(++endspec) == '^' )
335 negative_scanlist = true;
341 // TODO: This can run beyond a malformed format string
343 } while ( *endspec != ']' );
344 // read according to scanlist, equiv. to %s above
345 char * c = va_arg( status->arg, char * );
346 while ( ( status->current < status->width ) &&
347 ( ( rc = GET( status ) ) != EOF ) )
349 if ( negative_scanlist )
351 if ( IN_SCANSET( spec, endspec, rc ) )
359 if ( ! IN_SCANSET( spec, endspec, rc ) )
385 status->flags |= E_unsigned;
389 int * val = va_arg( status->arg, int * );
394 /* No conversion specifier. Bad conversion. */
398 if ( status->base != -1 )
400 /* integer conversion */
401 uintmax_t value = 0; /* absolute value read */
402 bool prefix_parsed = false;
404 while ( ( status->current < status->width ) &&
405 ( ( rc = GET( status ) ) != EOF ) )
411 /* matching sequence terminated by whitespace */
417 /* leading whitespace not counted against width */
423 /* no sign parsed yet */
433 /* not a sign; put back character */
439 else if ( ! prefix_parsed )
441 /* no prefix (0x... for hex, 0... for octal) parsed yet */
442 prefix_parsed = true;
445 /* not a prefix; if base not yet set, set to decimal */
446 if ( status->base == 0 )
454 /* starts with zero, so it might be a prefix. */
455 /* check what follows next (might be 0x...) */
456 if ( ( status->current < status->width ) &&
457 ( ( rc = GET( status ) ) != EOF ) )
459 if ( tolower( rc ) == 'x' )
461 /* 0x... would be prefix for hex base... */
462 if ( ( status->base == 0 ) ||
463 ( status->base == 16 ) )
469 /* ...unless already set to other value */
476 /* 0... but not 0x.... would be octal prefix */
478 if ( status->base == 0 )
482 /* in any case we have read a zero */
488 /* failed to read beyond the initial zero */
496 char * digitptr = memchr( _PDCLIB_digits, tolower( rc ), status->base );
497 if ( digitptr == NULL )
499 /* end of input item */
503 value *= status->base;
504 value += digitptr - _PDCLIB_digits;
508 /* width or input exhausted, or non-matching character */
509 if ( ! value_parsed )
511 /* out of input before anything could be parsed - input error */
512 /* FIXME: if first character does not match, value_parsed is not set - but it is NOT an input error */
513 if ( ( status->n == 0 ) && ( rc == EOF ) )
519 /* convert value to target type and assign to parameter */
520 if ( ! ( status->flags & E_suppressed ) )
522 switch ( status->flags & ( E_char | E_short | E_long | E_llong |
523 E_intmax | E_size | E_ptrdiff |
527 *( va_arg( status->arg, char * ) ) = (char)( value * sign );
529 case E_char | E_unsigned:
530 *( va_arg( status->arg, unsigned char * ) ) = (unsigned char)( value * sign );
534 *( va_arg( status->arg, short * ) ) = (short)( value * sign );
536 case E_short | E_unsigned:
537 *( va_arg( status->arg, unsigned short * ) ) = (unsigned short)( value * sign );
541 *( va_arg( status->arg, int * ) ) = (int)( value * sign );
544 *( va_arg( status->arg, unsigned int * ) ) = (unsigned int)( value * sign );
548 *( va_arg( status->arg, long * ) ) = (long)( value * sign );
550 case E_long | E_unsigned:
551 *( va_arg( status->arg, unsigned long * ) ) = (unsigned long)( value * sign );
555 *( va_arg( status->arg, long long * ) ) = (long long)( value * sign );
557 case E_llong | E_unsigned:
558 *( va_arg( status->arg, unsigned long long * ) ) = (unsigned long long)( value * sign );
562 *( va_arg( status->arg, intmax_t * ) ) = (intmax_t)( value * sign );
564 case E_intmax | E_unsigned:
565 *( va_arg( status->arg, uintmax_t * ) ) = (uintmax_t)( value * sign );
569 /* E_size always implies unsigned */
570 *( va_arg( status->arg, size_t * ) ) = (size_t)( value * sign );
574 /* E_ptrdiff always implies signed */
575 *( va_arg( status->arg, ptrdiff_t * ) ) = (ptrdiff_t)( value * sign );
579 puts( "UNSUPPORTED SCANF FLAG COMBINATION" );
580 return NULL; /* behaviour unspecified */
592 #define _PDCLIB_FILEID "_PDCLIB/scan.c"
593 #define _PDCLIB_STRINGIO
595 #include <_PDCLIB_test.h>
597 static int testscanf( char const * s, char const * format, ... )
599 struct _PDCLIB_status_t status;
602 status.s = (char *)s;
603 status.stream = NULL;
604 va_start( status.arg, format );
605 if ( *(_PDCLIB_scan( format, &status )) != '\0' )
607 printf( "_PDCLIB_scan() did not return end-of-specifier on '%s'.\n", format );
610 va_end( status.arg );
614 #define TEST_CONVERSION_ONLY
619 #include "scanf_testcases.h"