]> pd.if.org Git - pdclib/blob - functions/_PDCLIB/scan.c
Only the character groups still missing...
[pdclib] / functions / _PDCLIB / scan.c
1 /* $Id$ */
2
3 /* _PDCLIB_scan( const char *, struct _PDCLIB_status_t * )
4
5    This file is part of the Public Domain C Library (PDCLib).
6    Permission is granted to use, modify, and / or redistribute at will.
7 */
8
9 #include <stdio.h>
10 #include <stdbool.h>
11 #include <stdlib.h>
12 #include <stdarg.h>
13 #include <stdint.h>
14 #include <ctype.h>
15 #include <string.h>
16 #include <stddef.h>
17 #include <limits.h>
18
19 /* Using an integer's bits as flags for both the conversion flags and length
20    modifiers.
21 */
22 #define E_suppressed 1<<0
23 #define E_char       1<<6
24 #define E_short      1<<7
25 #define E_long       1<<8
26 #define E_llong      1<<9
27 #define E_intmax     1<<10
28 #define E_size       1<<11
29 #define E_ptrdiff    1<<12
30 #define E_intptr     1<<13
31 #define E_ldouble    1<<14
32 #define E_unsigned   1<<16
33
34
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.
40 */
41 #define ASSIGN_VALUE_TO( case_cond, type ) \
42     case case_cond: \
43         *( va_arg( status->arg, type * ) ) = (type)( value * sign ); \
44         break
45
46
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
50    strings.
51 */
52 static int GET( struct _PDCLIB_status_t * status )
53 {
54     int rc;
55     if ( status->stream != NULL )
56     {
57         rc = getc( status->stream );
58     }
59     else
60     {
61         rc = ( *status->s == '\0' ) ? EOF : (unsigned char)*((status->s)++);
62     }
63     if ( rc != EOF )
64     {
65         ++(status->i);
66         ++(status->this);
67     }
68     return rc;
69 }
70
71
72 /* Helper function to put a read character back into the string or stream,
73    whatever is used for input.
74 */
75 static void UNGET( int c, struct _PDCLIB_status_t * status )
76 {
77     if ( status->stream != NULL )
78     {
79         ungetc( c, status->stream ); /* TODO: Error? */
80     }
81     else
82     {
83         --(status->s);
84     }
85     --(status->i);
86     --(status->this);
87 }
88
89
90 const char * _PDCLIB_scan( const char * spec, struct _PDCLIB_status_t * status )
91 {
92     /* generic input character */
93     int rc;
94     const char * orig_spec = spec;
95     if ( *(++spec) == '%' )
96     {
97         /* %% -> match single '%' */
98         rc = GET( status );
99         switch ( rc )
100         {
101             case EOF:
102                 /* input error */
103                 if ( status->n == 0 )
104                 {
105                     status->n = -1;
106                 }
107                 return NULL;
108             case '%':
109                 return ++spec;
110             default:
111                 UNGET( rc, status );
112                 break;
113         }
114     }
115     /* Initializing status structure */
116     status->flags = 0;
117     status->base = -1;
118     status->this = 0;
119     status->width = 0;
120     status->prec = 0;
121
122     /* '*' suppresses assigning parsed value to variable */
123     if ( *spec == '*' )
124     {
125         status->flags |= E_suppressed;
126         ++spec;
127     }
128
129     /* If a width is given, strtol() will return its value. If not given,
130        strtol() will return zero. In both cases, endptr will point to the
131        rest of the conversion specifier - just what we need.
132     */
133     char const * prev_spec = spec;
134     status->width = (int)strtol( spec, (char**)&spec, 10 );
135     if ( spec == prev_spec )
136     {
137         status->width = SIZE_MAX;
138     }
139
140     /* Optional length modifier
141        We step one character ahead in any case, and step back only if we find
142        there has been no length modifier (or step ahead another character if it
143        has been "hh" or "ll").
144     */
145     switch ( *(spec++) )
146     {
147         case 'h':
148             if ( *spec == 'h' )
149             {
150                 /* hh -> char */
151                 status->flags |= E_char;
152                 ++spec;
153             }
154             else
155             {
156                 /* h -> short */
157                 status->flags |= E_short;
158             }
159             break;
160         case 'l':
161             if ( *spec == 'l' )
162             {
163                 /* ll -> long long */
164                 status->flags |= E_llong;
165                 ++spec;
166             }
167             else
168             {
169                 /* l -> long */
170                 status->flags |= E_long;
171             }
172             break;
173         case 'j':
174             /* j -> intmax_t, which might or might not be long long */
175             status->flags |= E_intmax;
176             break;
177         case 'z':
178             /* z -> size_t, which might or might not be unsigned int */
179             status->flags |= E_size;
180             break;
181         case 't':
182             /* t -> ptrdiff_t, which might or might not be long */
183             status->flags |= E_ptrdiff;
184             break;
185         case 'L':
186             /* L -> long double */
187             status->flags |= E_ldouble;
188             break;
189         default:
190             --spec;
191             break;
192     }
193
194     /* Conversion specifier */
195
196     /* whether valid input had been parsed */
197     bool value_parsed = false;
198
199     switch ( *spec )
200     {
201         case 'd':
202             status->base = 10;
203             break;
204         case 'i':
205             status->base = 0;
206             break;
207         case 'o':
208             status->base = 8;
209             status->flags |= E_unsigned;
210             break;
211         case 'u':
212             status->base = 10;
213             status->flags |= E_unsigned;
214             break;
215         case 'x':
216             status->base = 16;
217             status->flags |= E_unsigned;
218             break;
219         case 'f':
220         case 'F':
221         case 'e':
222         case 'E':
223         case 'g':
224         case 'G':
225         case 'a':
226         case 'A':
227             break;
228         case 'c':
229         {
230             char * c = va_arg( status->arg, char * );
231             /* for %c, default width is one */
232             if ( status->width == SIZE_MAX )
233             {
234                 status->width = 1;
235             }
236             /* reading until width reached or input exhausted */
237             while ( ( status->this < status->width ) &&
238                     ( ( rc = GET( status ) ) != EOF ) )
239             {
240                 *(c++) = rc;
241                 value_parsed = true;
242             }
243             /* width or input exhausted */
244             if ( value_parsed )
245             {
246                 ++status->n;
247                 return ++spec;
248             }
249             else
250             {
251                 /* input error, no character read */
252                 if ( status->n == 0 )
253                 {
254                     status->n = -1;
255                 }
256                 return NULL;
257             }
258         }
259         case 's':
260         {
261             char * c = va_arg( status->arg, char * );
262             while ( ( status->this < status->width ) && 
263                     ( ( rc = GET( status ) ) != EOF ) )
264             {
265                 if ( isspace( rc ) )
266                 {
267                     if ( value_parsed )
268                     {
269                         /* matching sequence terminated by whitespace */
270                         *c = '\0';
271                         return spec;
272                     }
273                     else
274                     {
275                         /* leading whitespace not counted against width */
276                         --(status->this);
277                     }
278                 }
279                 else
280                 {
281                     value_parsed = true;
282                     *(c++) = rc;
283                 }
284             }
285             /* width or input exhausted */
286             if ( value_parsed )
287             {
288                 *c = '\0';
289                 ++status->n;
290                 return ++spec;
291             }
292             else
293             {
294                 /* input error, no character read */
295                 if ( status->n == 0 )
296                 {
297                     status->n = -1;
298                 }
299                 return NULL;
300             }
301         }
302         case 'p':
303             status->base = 16;
304             status->flags |= E_unsigned;
305             break;
306         case 'n':
307         {
308             int * val = va_arg( status->arg, int * );
309             *val = status->i;
310             return ++spec;
311         }
312         default:
313             /* No conversion specifier. Bad conversion. */
314             return orig_spec;
315     }
316
317     if ( status->base != -1 )
318     {
319         /* integer conversion */
320         uintmax_t value = 0;         /* absolute value read */
321         uintmax_t limit;             /* max. value allowed */
322         uintmax_t threshold;         /* overflow threshold */
323         bool prefix_parsed = false;
324         int sign = 0;
325         while ( ( status->this < status->width ) &&
326                 ( ( rc = GET( status ) ) != EOF ) )
327         {
328             if ( isspace( rc ) )
329             {
330                 if ( sign )
331                 {
332                     /* matching sequence terminated by whitespace */
333                     UNGET( rc, status );
334                     break;
335                 }
336                 else
337                 {
338                     /* leading whitespace not counted against width */
339                     status->this--;
340                 }
341             }
342             else if ( ! sign )
343             {
344                 /* no sign parsed yet */
345                 switch ( rc )
346                 {
347                     case '-':
348                         sign = -1;
349                         break;
350                     case '+':
351                         sign = 1;
352                         break;
353                     default:
354                         /* not a sign; put back character */
355                         sign = 1;
356                         UNGET( rc, status );
357                         break;
358                 }
359                 switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_intmax | E_size | E_ptrdiff | E_unsigned ) )
360                 {
361                     case E_char:
362                         limit = ( sign == 1 ) ? CHAR_MAX : ( CHAR_MIN * sign );
363                         break;
364                     case E_char | E_unsigned:
365                         limit = UCHAR_MAX;
366                         break;
367                     case E_short:
368                         limit = ( sign == 1 ) ? SHRT_MAX : ( SHRT_MIN * sign );
369                         break;
370                     case E_short | E_unsigned:
371                         limit = USHRT_MAX;
372                         break;
373                     case E_long:
374                         limit = ( sign == 1 ) ? LONG_MAX : ( LONG_MIN * sign );
375                         break;
376                     case E_long | E_unsigned:
377                         limit = ULONG_MAX;
378                         break;
379                     case E_llong:
380                         limit = ( sign == 1 ) ? LLONG_MAX : ( LLONG_MIN * sign );
381                         break;
382                     case E_llong | E_unsigned:
383                         limit = ULLONG_MAX;
384                         break;
385                     case E_intmax:
386                         limit = ( sign == 1 ) ? INTMAX_MAX : ( INTMAX_MIN * sign );
387                         break;
388                     case E_intmax | E_unsigned:
389                         limit = UINTMAX_MAX;
390                         break;
391                     case E_size:
392                     case E_size | E_unsigned:
393                         limit = SIZE_MAX;
394                         break;
395                     case E_ptrdiff:
396                     case E_ptrdiff | E_unsigned:
397                         limit = ( sign == 1 ) ? PTRDIFF_MAX : ( PTRDIFF_MIN * sign );
398                         break;
399                     case E_unsigned:
400                         limit = UINT_MAX;
401                         break;
402                     default:
403                         limit = ( sign == 1 ) ? INT_MAX : ( INT_MIN * sign );
404                         break;
405                 }
406             }
407             else if ( ! prefix_parsed )
408             {
409                 /* no prefix (0x... for hex, 0... for octal) parsed yet */
410                 prefix_parsed = true;
411                 if ( rc != '0' )
412                 {
413                     /* not a prefix; if base not yet set, set to decimal */
414                     if ( status->base == 0 )
415                     {
416                         status->base = 10;
417                     }
418                     UNGET( rc, status );
419                 }
420                 else
421                 {
422                     /* starts with zero, so it might be a prefix. */
423                     /* check what follows next (might be 0x...) */
424                     if ( ( status->this < status->width ) &&
425                          ( ( rc = GET( status ) ) != EOF ) )
426                     {
427                         if ( tolower( rc ) == 'x' )
428                         {
429                             /* 0x... would be prefix for hex base... */
430                             if ( ( status->base == 0 ) ||
431                                  ( status->base == 16 ) )
432                             {
433                                 status->base = 16;
434                             }
435                             else
436                             {
437                                 /* ...unless already set to other value */
438                                 UNGET( rc, status );
439                                 value_parsed = true;
440                             }
441                         }
442                         else
443                         {
444                             /* 0... but not 0x.... would be octal prefix */
445                             UNGET( rc, status );
446                             if ( status->base == 0 )
447                             {
448                                 status->base = 8;
449                             }
450                             /* in any case we have read a zero */
451                             value_parsed = true;
452                         }
453                     }
454                     else
455                     {
456                         /* failed to read beyond the initial zero */
457                         value_parsed = true;
458                         break;
459                     }
460                 }
461             }
462             else
463             {
464                 char * digitptr = memchr( _PDCLIB_digits, tolower( rc ), status->base );
465                 if ( digitptr == NULL )
466                 {
467                     /* end of input item */
468                     UNGET( rc, status );
469                     break;
470                 }
471                 // SOLAR
472                 // if ( ( ( limit - ( digitptr - _PDCLIB_digits ) ) / status->base ) >= value )
473                 //if ( ( ( limit / status->base ) >= value ) && ( ( limit - ( digitptr - _PDCLIB_digits ) ) >= ( value * status->base ) ) )
474                 {
475                     /* no overflow */
476                     value *= status->base;
477                     value += digitptr - _PDCLIB_digits;
478                     value_parsed = true;
479                 }
480                 //else
481                 //{
482                 //    value = limit;
483                 //    threshold = 0;
484                 //}
485             }
486         }
487         /* width or input exhausted, or non-matching character */
488         if ( ! value_parsed )
489         {
490             /* out of input before anything could be parsed - input error */
491             /* FIXME: if first character does not match, value_parsed is not set - but it is NOT an input error */
492             if ( ( status->n == 0 ) && ( rc == EOF ) )
493             {
494                 status->n = -1;
495             }
496             return NULL;
497         }
498         /* convert value to target type and assign to parameter */
499         switch ( status->flags & ( E_char | E_short | E_long | E_llong |
500                                    E_intmax | E_size | E_ptrdiff |
501                                    E_unsigned ) )
502         {
503             ASSIGN_VALUE_TO( E_char, char );
504             ASSIGN_VALUE_TO( E_char | E_unsigned, unsigned char );
505             ASSIGN_VALUE_TO( E_short, short );
506             ASSIGN_VALUE_TO( E_short | E_unsigned, unsigned short );
507             ASSIGN_VALUE_TO( 0, int );
508             ASSIGN_VALUE_TO( E_unsigned, unsigned int );
509             ASSIGN_VALUE_TO( E_long, long );
510             ASSIGN_VALUE_TO( E_long | E_unsigned, unsigned long );
511             ASSIGN_VALUE_TO( E_llong, long long );
512             ASSIGN_VALUE_TO( E_llong | E_unsigned, unsigned long long );
513             ASSIGN_VALUE_TO( E_intmax, intmax_t );
514             ASSIGN_VALUE_TO( E_intmax | E_unsigned, uintmax_t );
515             ASSIGN_VALUE_TO( E_size, size_t );
516             /* ASSIGN_VALUE_TO( E_size | E_unsigned, unsigned size_t ); */
517             ASSIGN_VALUE_TO( E_ptrdiff, ptrdiff_t );
518             /* ASSIGN_VALUE_TO( E_ptrdiff | E_unsigned, unsigned ptrdiff_t ); */
519             default:
520                 puts( "UNSUPPORTED SCANF FLAG COMBINATION" );
521                 return NULL; /* behaviour unspecified */
522         }
523         ++(status->n);
524         return ++spec;
525     }
526     /* TODO: Floats. */
527     return NULL;
528 }
529
530
531 #ifdef TEST
532 #include <_PDCLIB_test.h>
533 #include <limits.h>
534
535
536  
537 int main( void )
538 {
539     /* Testing covered by fscanf.c */
540     return TEST_RESULTS;
541 }
542
543 #endif