#include <stdbool.h>
#include <stdlib.h>
#include <stdarg.h>
+#include <stdint.h>
+#include <ctype.h>
+#include <string.h>
+#include <stddef.h>
/* Using an integer's bits as flags for both the conversion flags and length
modifiers.
#define E_unsigned 1<<16
-static bool MATCH( const char x, struct _PDCLIB_status_t * status )
+#define ASSIGN( case_cond, type ) \
+ case case_cond: \
+ *( va_arg( status->arg, type * ) ) = (type)( value * sign ); \
+ break
+
+
+static int GET( struct _PDCLIB_status_t * status )
{
+ ++(status->i);
+ ++(status->this);
if ( status->stream != NULL )
{
- /* Matching against stream */
- if ( status->stream->buffer[ status->stream->bufidx ] == x )
- {
- getc( status->stream );
- ++(status->i);
- return true;
- }
- else
- {
- return false;
- }
+ return getc( status->stream );
}
else
{
- /* Matching against string */
- if ( *(status->s) == x )
- {
- ++(status->s);
- ++(status->i);
- return true;
- }
- else
- {
- return false;
- }
+ return *((status->s)++);
}
}
+static void UNGET( int c, struct _PDCLIB_status_t * status )
+{
+ if ( status->stream != NULL )
+ {
+ ungetc( c, status->stream ); /* TODO: Error? */
+ }
+ else
+ {
+ *(--(status->s)) = c;
+ }
+ --(status->i);
+ --(status->this);
+}
+
+
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:
+ /* matching failure */
+ return NULL;
+ case '%':
+ return ++spec;
+ default:
+ UNGET( rc, status );
+ break;
+ }
}
/* Initializing status structure */
status->flags = 0;
- status->base = 0;
+ status->base = -1;
status->this = 0;
status->width = 0;
status->prec = 0;
}
/* Conversion specifier */
+
+ /* whether valid input had been parsed */
+ bool value_parsed = false;
+
switch ( *spec )
{
case 'd':
status->base = 10;
break;
case 'i':
- /* status->base = 0; */
+ status->base = 0;
break;
case 'o':
status->base = 8;
case 'A':
break;
case 'c':
- /* TODO */
- break;
+ {
+ char * c = va_arg( status->arg, char * );
+ if ( status->width == SIZE_MAX )
+ {
+ status->width = 1;
+ }
+ while ( ( status->this < status->width ) &&
+ ( ( rc = GET( status ) ) != EOF ) )
+ {
+ *(c++) = rc;
+ value_parsed = true;
+ }
+ if ( value_parsed )
+ {
+ ++status->n;
+ return ++spec;
+ }
+ else
+ {
+ return NULL;
+ }
+ }
case 's':
- /* TODO */
- break;
+ {
+ char * c = va_arg( status->arg, char * );
+ while ( ( status->this < status->width ) &&
+ ( ( rc = GET( status ) ) != EOF ) )
+ {
+ if ( isspace( rc ) )
+ {
+ if ( value_parsed )
+ {
+ *c = '\0';
+ return spec;
+ }
+ else
+ {
+ --(status->this);
+ }
+ }
+ else
+ {
+ value_parsed = true;
+ *(c++) = rc;
+ }
+ }
+ /* width or input exhausted */
+ if ( value_parsed )
+ {
+ *c = '\0';
+ ++status->n;
+ return ++spec;
+ }
+ else
+ {
+ return NULL;
+ }
+ }
case 'p':
- /* TODO */
status->base = 16;
status->flags |= E_unsigned;
break;
/* No conversion specifier. Bad conversion. */
return orig_spec;
}
- /* TODO: Actual conversions */
+
+ if ( status->base != -1 )
+ {
+ /* integer conversion */
+ uintmax_t value = 0;
+ bool prefix_parsed = false;
+ int sign = 0;
+ while ( ( status->this < status->width ) &&
+ ( ( rc = GET( status ) ) != EOF ) )
+ {
+ if ( ! sign )
+ {
+ switch ( rc )
+ {
+ case '-':
+ sign = -1;
+ break;
+ case '+':
+ sign = 1;
+ break;
+ default:
+ sign = 1;
+ UNGET( rc, status );
+ break;
+ }
+ }
+ else if ( ! prefix_parsed )
+ {
+ prefix_parsed = true;
+ if ( rc != '0' )
+ {
+ if ( status->base == 0 )
+ {
+ status->base = 10;
+ }
+ UNGET( rc, status );
+ }
+ else
+ {
+ if ( ( status->this < status->width ) &&
+ ( ( rc = GET( status ) ) != EOF ) )
+ {
+ if ( tolower( rc ) == 'x' )
+ {
+ if ( ( status->base == 0 ) ||
+ ( status->base == 16 ) )
+ {
+ status->base = 16;
+ }
+ else
+ {
+ UNGET( rc, status );
+ value_parsed = true;
+ }
+ }
+ else
+ {
+ UNGET( rc, status );
+ if ( status->base == 0 )
+ {
+ status->base = 8;
+ }
+ value_parsed = true;
+ }
+ }
+ }
+ }
+ else
+ {
+ char * digitptr = memchr( _PDCLIB_digits, tolower( rc ), status->base );
+ if ( digitptr == NULL )
+ {
+ /* end of input item */
+ break;
+ }
+ value *= status->base;
+ value += digitptr - _PDCLIB_digits;
+ value_parsed = true;
+ }
+ }
+ /* width exceeded, EOF, read error, non-matching character */
+ if ( ! value_parsed )
+ {
+ /* matching error */
+ return NULL;
+ }
+ switch ( status->flags & ( E_char | E_short | E_long | E_llong |
+ E_intmax | E_size | E_ptrdiff |
+ E_unsigned ) )
+ {
+ ASSIGN( E_char, char );
+ ASSIGN( E_char | E_unsigned, unsigned char );
+ ASSIGN( E_short, short );
+ ASSIGN( E_short | E_unsigned, unsigned short );
+ ASSIGN( 0, int );
+ ASSIGN( E_unsigned, unsigned int );
+ ASSIGN( E_long, long );
+ ASSIGN( E_long | E_unsigned, unsigned long );
+ ASSIGN( E_llong, long long );
+ ASSIGN( E_llong | E_unsigned, unsigned long long );
+ ASSIGN( E_intmax, intmax_t );
+ ASSIGN( E_intmax | E_unsigned, uintmax_t );
+ ASSIGN( E_size, size_t );
+ /* ASSIGN( E_size | E_unsigned, unsigned size_t ); */
+ ASSIGN( E_ptrdiff, ptrdiff_t );
+ /* ASSIGN( E_ptrdiff | E_unsigned, unsigned ptrdiff_t ); */
+ default:
+ puts( "UNSUPPORTED SCANF FLAG COMBINATION" );
+ return NULL;
+ }
+ return ++spec;
+ }
+ /* TODO: Floats. */
return NULL;
}
+
#ifdef TEST
#include <_PDCLIB_test.h>
+#include <limits.h>
+
+
int main( void )
{
- TESTCASE( NO_TESTDRIVER );
+ /* Testing covered by fscanf.c */
return TEST_RESULTS;
}