X-Git-Url: https://pd.if.org/git/?a=blobdiff_plain;f=functions%2F_PDCLIB%2Fscan.c;h=8d4d7e0b98b3de5db39ff07fd71c83cadefb68cd;hb=5596d52a975696c29adc741ac1cdb49fb7315d73;hp=523b0c81449c5846f50868f09118fcb445c69dba;hpb=7f5e4748e1e44b136cbeddc70601bbf2a5a56e1b;p=pdclib diff --git a/functions/_PDCLIB/scan.c b/functions/_PDCLIB/scan.c index 523b0c8..8d4d7e0 100644 --- a/functions/_PDCLIB/scan.c +++ b/functions/_PDCLIB/scan.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /* _PDCLIB_scan( const char *, struct _PDCLIB_status_t * ) This file is part of the Public Domain C Library (PDCLib). @@ -10,7 +8,11 @@ #include #include #include +#include #include +#include +#include +#include /* Using an integer's bits as flags for both the conversion flags and length modifiers. @@ -23,50 +25,39 @@ #define E_intmax 1<<10 #define E_size 1<<11 #define E_ptrdiff 1<<12 -#define E_intptr 1<<13 +#define E_pointer 1<<13 #define E_ldouble 1<<14 #define E_unsigned 1<<16 -#define MATCH_FAIL -1 -#define MATCH_ERROR -2 - -static int MATCH( int c, struct _PDCLIB_status_t * status ) +/* Helper function to get a character from the string or stream, whatever is + used for input. When reading from a string, returns EOF on end-of-string + so that handling of the return value can be uniform for both streams and + strings. +*/ +static int GET( struct _PDCLIB_status_t * status ) { + int rc = EOF; if ( status->stream != NULL ) { - if ( ! _PDCLIB_prepread( status->stream ) ) - { - return MATCH_ERROR; - } - if ( tolower( status->stream->buffer[ status->stream->bufidx ] ) == c ) - { - /* recycling parameter */ - c = getc( status->stream ); - } - else - { - return MATCH_FAIL; - } + rc = getc( status->stream ); } else { - if ( tolower( *(status->s) ) == c ) - { - /* recycling parameter */ - c = *((status->s)++); /* TODO: \0 */ - } - else - { - return MATCH_FAIL; - } + rc = ( *status->s == '\0' ) ? EOF : (unsigned char)*((status->s)++); + } + if ( rc != EOF ) + { + ++(status->i); + ++(status->current); } - ++(status->i); - ++(status->this); - return c; + return rc; } +/* Helper function to put a read character back into the string or stream, + whatever is used for input. +*/ static void UNGET( int c, struct _PDCLIB_status_t * status ) { if ( status->stream != NULL ) @@ -75,26 +66,80 @@ static void UNGET( int c, struct _PDCLIB_status_t * status ) } else { - *(--(status->s)) = c; + --(status->s); } --(status->i); - --(status->this); + --(status->current); +} + + +/* Helper function to check if a character is part of a given scanset */ +static bool IN_SCANSET( const char * scanlist, const char * end_scanlist, int rc ) +{ + // SOLAR + int previous = -1; + while ( scanlist != end_scanlist ) + { + if ( ( *scanlist == '-' ) && ( previous != -1 ) ) + { + /* possible scangroup ("a-z") */ + if ( ++scanlist == end_scanlist ) + { + /* '-' at end of scanlist does not describe a scangroup */ + return rc == '-'; + } + while ( ++previous <= (unsigned char)*scanlist ) + { + if ( previous == rc ) + { + return true; + } + } + previous = -1; + } + else + { + /* not a scangroup, check verbatim */ + if ( rc == (unsigned char)*scanlist ) + { + return true; + } + previous = (unsigned char)(*scanlist++); + } + } + return false; } const char * _PDCLIB_scan( const char * spec, struct _PDCLIB_status_t * status ) { + /* generic input character */ + int rc; const char * orig_spec = spec; if ( *(++spec) == '%' ) { /* %% -> match single '%' */ - MATCH( *spec, status ); - return ++spec; + rc = GET( status ); + switch ( rc ) + { + case EOF: + /* input error */ + if ( status->n == 0 ) + { + status->n = -1; + } + return NULL; + case '%': + return ++spec; + default: + UNGET( rc, status ); + break; + } } /* Initializing status structure */ status->flags = 0; status->base = -1; - status->this = 0; + status->current = 0; status->width = 0; status->prec = 0; @@ -109,7 +154,12 @@ const char * _PDCLIB_scan( const char * spec, struct _PDCLIB_status_t * status ) strtol() will return zero. In both cases, endptr will point to the rest of the conversion specifier - just what we need. */ + char const * prev_spec = spec; status->width = (int)strtol( spec, (char**)&spec, 10 ); + if ( spec == prev_spec ) + { + status->width = SIZE_MAX; + } /* Optional length modifier We step one character ahead in any case, and step back only if we find @@ -166,6 +216,10 @@ const char * _PDCLIB_scan( const char * spec, struct _PDCLIB_status_t * status ) } /* Conversion specifier */ + + /* whether valid input had been parsed */ + bool value_parsed = false; + switch ( *spec ) { case 'd': @@ -196,15 +250,139 @@ const char * _PDCLIB_scan( const char * spec, struct _PDCLIB_status_t * status ) case 'A': break; case 'c': - /* TODO */ - break; + { + char * c = va_arg( status->arg, char * ); + /* for %c, default width is one */ + if ( status->width == SIZE_MAX ) + { + status->width = 1; + } + /* reading until width reached or input exhausted */ + while ( ( status->current < status->width ) && + ( ( rc = GET( status ) ) != EOF ) ) + { + *(c++) = rc; + value_parsed = true; + } + /* width or input exhausted */ + if ( value_parsed ) + { + ++status->n; + return ++spec; + } + else + { + /* input error, no character read */ + if ( status->n == 0 ) + { + status->n = -1; + } + return NULL; + } + } case 's': - /* TODO */ - break; + { + char * c = va_arg( status->arg, char * ); + while ( ( status->current < status->width ) && + ( ( rc = GET( status ) ) != EOF ) ) + { + if ( isspace( rc ) ) + { + UNGET( rc, status ); + if ( value_parsed ) + { + /* matching sequence terminated by whitespace */ + *c = '\0'; + ++status->n; + return ++spec; + } + else + { + /* matching error */ + return NULL; + } + } + else + { + /* match */ + value_parsed = true; + *(c++) = rc; + } + } + /* width or input exhausted */ + if ( value_parsed ) + { + *c = '\0'; + ++status->n; + return ++spec; + } + else + { + /* input error, no character read */ + if ( status->n == 0 ) + { + status->n = -1; + } + return NULL; + } + } + case '[': + { + const char * endspec = spec; + bool negative_scanlist = false; + if ( *(++endspec) == '^' ) + { + negative_scanlist = true; + ++endspec; + } + spec = endspec; + do + { + // TODO: This can run beyond a malformed format string + ++endspec; + } while ( *endspec != ']' ); + // read according to scanlist, equiv. to %s above + char * c = va_arg( status->arg, char * ); + while ( ( status->current < status->width ) && + ( ( rc = GET( status ) ) != EOF ) ) + { + if ( negative_scanlist ) + { + if ( IN_SCANSET( spec, endspec, rc ) ) + { + UNGET( rc, status ); + break; + } + } + else + { + if ( ! IN_SCANSET( spec, endspec, rc ) ) + { + UNGET( rc, status ); + break; + } + } + value_parsed = true; + *(c++) = rc; + } + if ( value_parsed ) + { + *c = '\0'; + ++status->n; + return ++endspec; + } + else + { + if ( rc == EOF ) + { + status->n = -1; + } + return NULL; + } + } case 'p': - /* TODO */ status->base = 16; - status->flags |= E_unsigned; + status->flags |= E_pointer; break; case 'n': { @@ -216,67 +394,234 @@ const char * _PDCLIB_scan( const char * spec, struct _PDCLIB_status_t * status ) /* No conversion specifier. Bad conversion. */ return orig_spec; } - bool zero = false; + if ( status->base != -1 ) { - bool value = false; - int rc; - if ( ( rc = MATCH( '0', status ) ) >= 0 ) + /* integer conversion */ + uintmax_t value = 0; /* absolute value read */ + bool prefix_parsed = false; + int sign = 0; + while ( ( status->current < status->width ) && + ( ( rc = GET( status ) ) != EOF ) ) { - if ( ( rc = MATCH( 'x', status ) ) >= 0 ) + if ( isspace( rc ) ) { - if ( ( status->base == 0 ) || ( status->base == 16 ) ) + if ( sign ) { - status->base = 16; + /* matching sequence terminated by whitespace */ + UNGET( rc, status ); + break; } else { - UNGET( rc, status ); - value = true; + /* leading whitespace not counted against width */ + status->current--; } } - else if ( rc == MATCH_FAIL ) + else if ( ! sign ) { - if ( status->base == 0 ) + /* no sign parsed yet */ + switch ( rc ) { - status->base = 8; + case '-': + sign = -1; + break; + case '+': + sign = 1; + break; + default: + /* not a sign; put back character */ + sign = 1; + UNGET( rc, status ); + break; + } + } + else if ( ! prefix_parsed ) + { + /* no prefix (0x... for hex, 0... for octal) parsed yet */ + prefix_parsed = true; + if ( rc != '0' ) + { + /* not a prefix; if base not yet set, set to decimal */ + if ( status->base == 0 ) + { + status->base = 10; + } + UNGET( rc, status ); } else { - value = true; + /* starts with zero, so it might be a prefix. */ + /* check what follows next (might be 0x...) */ + if ( ( status->current < status->width ) && + ( ( rc = GET( status ) ) != EOF ) ) + { + if ( tolower( rc ) == 'x' ) + { + /* 0x... would be prefix for hex base... */ + if ( ( status->base == 0 ) || + ( status->base == 16 ) ) + { + status->base = 16; + } + else + { + /* ...unless already set to other value */ + UNGET( rc, status ); + value_parsed = true; + } + } + else + { + /* 0... but not 0x.... would be octal prefix */ + UNGET( rc, status ); + if ( status->base == 0 ) + { + status->base = 8; + } + /* in any case we have read a zero */ + value_parsed = true; + } + } + else + { + /* failed to read beyond the initial zero */ + value_parsed = true; + break; + } } } else { - /* TODO: MATCH_ERROR */ + char * digitptr = memchr( _PDCLIB_digits, tolower( rc ), status->base ); + if ( digitptr == NULL ) + { + /* end of input item */ + UNGET( rc, status ); + break; + } + value *= status->base; + value += digitptr - _PDCLIB_digits; + value_parsed = true; } } - else if ( rc == MATCH_FAIL ) + /* width or input exhausted, or non-matching character */ + if ( ! value_parsed ) { - if ( status->base == 0 ) + /* out of input before anything could be parsed - input error */ + /* FIXME: if first character does not match, value_parsed is not set - but it is NOT an input error */ + if ( ( status->n == 0 ) && ( rc == EOF ) ) { - status->base = 10; + status->n = -1; } + return NULL; } - else + /* convert value to target type and assign to parameter */ + if ( ! ( status->flags & E_suppressed ) ) { - /* TODO: MATCH_ERROR */ + switch ( status->flags & ( E_char | E_short | E_long | E_llong | + E_intmax | E_size | E_ptrdiff | E_pointer | + E_unsigned ) ) + { + case E_char: + *( va_arg( status->arg, char * ) ) = (char)( value * sign ); + break; + case E_char | E_unsigned: + *( va_arg( status->arg, unsigned char * ) ) = (unsigned char)( value * sign ); + break; + + case E_short: + *( va_arg( status->arg, short * ) ) = (short)( value * sign ); + break; + case E_short | E_unsigned: + *( va_arg( status->arg, unsigned short * ) ) = (unsigned short)( value * sign ); + break; + + case 0: + *( va_arg( status->arg, int * ) ) = (int)( value * sign ); + break; + case E_unsigned: + *( va_arg( status->arg, unsigned int * ) ) = (unsigned int)( value * sign ); + break; + + case E_long: + *( va_arg( status->arg, long * ) ) = (long)( value * sign ); + break; + case E_long | E_unsigned: + *( va_arg( status->arg, unsigned long * ) ) = (unsigned long)( value * sign ); + break; + + case E_llong: + *( va_arg( status->arg, long long * ) ) = (long long)( value * sign ); + break; + case E_llong | E_unsigned: + *( va_arg( status->arg, unsigned long long * ) ) = (unsigned long long)( value * sign ); + break; + + case E_intmax: + *( va_arg( status->arg, intmax_t * ) ) = (intmax_t)( value * sign ); + break; + case E_intmax | E_unsigned: + *( va_arg( status->arg, uintmax_t * ) ) = (uintmax_t)( value * sign ); + break; + + case E_size: + /* E_size always implies unsigned */ + *( va_arg( status->arg, size_t * ) ) = (size_t)( value * sign ); + break; + + case E_ptrdiff: + /* E_ptrdiff always implies signed */ + *( va_arg( status->arg, ptrdiff_t * ) ) = (ptrdiff_t)( value * sign ); + break; + + case E_pointer: + /* E_pointer always implies unsigned */ + *( uintptr_t* )( va_arg( status->arg, void * ) ) = (uintptr_t)( value * sign ); + break; + + default: + puts( "UNSUPPORTED SCANF FLAG COMBINATION" ); + return NULL; /* behaviour unspecified */ + } + ++(status->n); } - /* TODO: Integer conversion */ - } - else - { - /* TODO: Float conversions? */ + return ++spec; } + /* TODO: Floats. */ return NULL; } + #ifdef TEST +#define _PDCLIB_FILEID "_PDCLIB/scan.c" +#define _PDCLIB_STRINGIO + #include <_PDCLIB_test.h> +static int testscanf( char const * s, char const * format, ... ) +{ + struct _PDCLIB_status_t status; + status.n = 0; + status.i = 0; + status.s = (char *)s; + status.stream = NULL; + va_start( status.arg, format ); + if ( *(_PDCLIB_scan( format, &status )) != '\0' ) + { + printf( "_PDCLIB_scan() did not return end-of-specifier on '%s'.\n", format ); + ++TEST_RESULTS; + } + va_end( status.arg ); + return status.n; +} + +#define TEST_CONVERSION_ONLY + int main( void ) { - TESTCASE( NO_TESTDRIVER ); + char source[100]; +#include "scanf_testcases.h" return TEST_RESULTS; }