]> pd.if.org Git - pdclib/blob - functions/_PDCLIB/scan.c
Some fixes to string scanning. First half of scanset scanning.
[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 /* 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 )
92 {
93     // SOLAR
94     return true;
95 }
96
97
98 const char * _PDCLIB_scan( const char * spec, struct _PDCLIB_status_t * status )
99 {
100     /* generic input character */
101     int rc;
102     const char * orig_spec = spec;
103     if ( *(++spec) == '%' )
104     {
105         /* %% -> match single '%' */
106         rc = GET( status );
107         switch ( rc )
108         {
109             case EOF:
110                 /* input error */
111                 if ( status->n == 0 )
112                 {
113                     status->n = -1;
114                 }
115                 return NULL;
116             case '%':
117                 return ++spec;
118             default:
119                 UNGET( rc, status );
120                 break;
121         }
122     }
123     /* Initializing status structure */
124     status->flags = 0;
125     status->base = -1;
126     status->this = 0;
127     status->width = 0;
128     status->prec = 0;
129
130     /* '*' suppresses assigning parsed value to variable */
131     if ( *spec == '*' )
132     {
133         status->flags |= E_suppressed;
134         ++spec;
135     }
136
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.
140     */
141     char const * prev_spec = spec;
142     status->width = (int)strtol( spec, (char**)&spec, 10 );
143     if ( spec == prev_spec )
144     {
145         status->width = SIZE_MAX;
146     }
147
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").
152     */
153     switch ( *(spec++) )
154     {
155         case 'h':
156             if ( *spec == 'h' )
157             {
158                 /* hh -> char */
159                 status->flags |= E_char;
160                 ++spec;
161             }
162             else
163             {
164                 /* h -> short */
165                 status->flags |= E_short;
166             }
167             break;
168         case 'l':
169             if ( *spec == 'l' )
170             {
171                 /* ll -> long long */
172                 status->flags |= E_llong;
173                 ++spec;
174             }
175             else
176             {
177                 /* l -> long */
178                 status->flags |= E_long;
179             }
180             break;
181         case 'j':
182             /* j -> intmax_t, which might or might not be long long */
183             status->flags |= E_intmax;
184             break;
185         case 'z':
186             /* z -> size_t, which might or might not be unsigned int */
187             status->flags |= E_size;
188             break;
189         case 't':
190             /* t -> ptrdiff_t, which might or might not be long */
191             status->flags |= E_ptrdiff;
192             break;
193         case 'L':
194             /* L -> long double */
195             status->flags |= E_ldouble;
196             break;
197         default:
198             --spec;
199             break;
200     }
201
202     /* Conversion specifier */
203
204     /* whether valid input had been parsed */
205     bool value_parsed = false;
206
207     switch ( *spec )
208     {
209         case 'd':
210             status->base = 10;
211             break;
212         case 'i':
213             status->base = 0;
214             break;
215         case 'o':
216             status->base = 8;
217             status->flags |= E_unsigned;
218             break;
219         case 'u':
220             status->base = 10;
221             status->flags |= E_unsigned;
222             break;
223         case 'x':
224             status->base = 16;
225             status->flags |= E_unsigned;
226             break;
227         case 'f':
228         case 'F':
229         case 'e':
230         case 'E':
231         case 'g':
232         case 'G':
233         case 'a':
234         case 'A':
235             break;
236         case 'c':
237         {
238             char * c = va_arg( status->arg, char * );
239             /* for %c, default width is one */
240             if ( status->width == SIZE_MAX )
241             {
242                 status->width = 1;
243             }
244             /* reading until width reached or input exhausted */
245             while ( ( status->this < status->width ) &&
246                     ( ( rc = GET( status ) ) != EOF ) )
247             {
248                 *(c++) = rc;
249                 value_parsed = true;
250             }
251             /* width or input exhausted */
252             if ( value_parsed )
253             {
254                 ++status->n;
255                 return ++spec;
256             }
257             else
258             {
259                 /* input error, no character read */
260                 if ( status->n == 0 )
261                 {
262                     status->n = -1;
263                 }
264                 return NULL;
265             }
266         }
267         case 's':
268         {
269             char * c = va_arg( status->arg, char * );
270             while ( ( status->this < status->width ) && 
271                     ( ( rc = GET( status ) ) != EOF ) )
272             {
273                 if ( isspace( rc ) )
274                 {
275                     UNGET( rc, status );
276                     if ( value_parsed )
277                     {
278                         /* matching sequence terminated by whitespace */
279                         *c = '\0';
280                         ++status->n;
281                         return ++spec;
282                     }
283                     else
284                     {
285                         /* matching error */
286                         return NULL;
287                     }
288                 }
289                 else
290                 {
291                     /* match */
292                     value_parsed = true;
293                     *(c++) = rc;
294                 }
295             }
296             /* width or input exhausted */
297             if ( value_parsed )
298             {
299                 *c = '\0';
300                 ++status->n;
301                 return ++spec;
302             }
303             else
304             {
305                 /* input error, no character read */
306                 if ( status->n == 0 )
307                 {
308                     status->n = -1;
309                 }
310                 return NULL;
311             }
312         }
313         case '[':
314         {
315             const char * endspec = spec;
316             bool negative_scanlist = false;
317             if ( *(++endspec) == '^' )
318             {
319                 negative_scanlist = true;
320                 ++endspec;
321             }
322             spec = endspec;
323             do
324             {
325                 // TODO: This can run beyond a malformed format string
326                 ++endspec;
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 ) )
332             {
333                 if ( NOT_IN_SCANSET( spec, endspec, negative_scanlist, rc ) )
334                 {
335                     break;
336                 }
337                 value_parsed = true;
338                 *(c++) = rc;
339             }
340             if ( value_parsed )
341             {
342                 *c = '\0';
343                 ++status->n;
344                 return ++endspec;
345             }
346             else
347             {
348                 if ( status->n == 0 )
349                 {
350                     status->n = -1;
351                 }
352                 return NULL;
353             }
354         }
355         case 'p':
356             status->base = 16;
357             status->flags |= E_unsigned;
358             break;
359         case 'n':
360         {
361             int * val = va_arg( status->arg, int * );
362             *val = status->i;
363             return ++spec;
364         }
365         default:
366             /* No conversion specifier. Bad conversion. */
367             return orig_spec;
368     }
369
370     if ( status->base != -1 )
371     {
372         /* integer conversion */
373         uintmax_t value = 0;         /* absolute value read */
374         bool prefix_parsed = false;
375         int sign = 0;
376         while ( ( status->this < status->width ) &&
377                 ( ( rc = GET( status ) ) != EOF ) )
378         {
379             if ( isspace( rc ) )
380             {
381                 if ( sign )
382                 {
383                     /* matching sequence terminated by whitespace */
384                     UNGET( rc, status );
385                     break;
386                 }
387                 else
388                 {
389                     /* leading whitespace not counted against width */
390                     status->this--;
391                 }
392             }
393             else if ( ! sign )
394             {
395                 /* no sign parsed yet */
396                 switch ( rc )
397                 {
398                     case '-':
399                         sign = -1;
400                         break;
401                     case '+':
402                         sign = 1;
403                         break;
404                     default:
405                         /* not a sign; put back character */
406                         sign = 1;
407                         UNGET( rc, status );
408                         break;
409                 }
410             }
411             else if ( ! prefix_parsed )
412             {
413                 /* no prefix (0x... for hex, 0... for octal) parsed yet */
414                 prefix_parsed = true;
415                 if ( rc != '0' )
416                 {
417                     /* not a prefix; if base not yet set, set to decimal */
418                     if ( status->base == 0 )
419                     {
420                         status->base = 10;
421                     }
422                     UNGET( rc, status );
423                 }
424                 else
425                 {
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 ) )
430                     {
431                         if ( tolower( rc ) == 'x' )
432                         {
433                             /* 0x... would be prefix for hex base... */
434                             if ( ( status->base == 0 ) ||
435                                  ( status->base == 16 ) )
436                             {
437                                 status->base = 16;
438                             }
439                             else
440                             {
441                                 /* ...unless already set to other value */
442                                 UNGET( rc, status );
443                                 value_parsed = true;
444                             }
445                         }
446                         else
447                         {
448                             /* 0... but not 0x.... would be octal prefix */
449                             UNGET( rc, status );
450                             if ( status->base == 0 )
451                             {
452                                 status->base = 8;
453                             }
454                             /* in any case we have read a zero */
455                             value_parsed = true;
456                         }
457                     }
458                     else
459                     {
460                         /* failed to read beyond the initial zero */
461                         value_parsed = true;
462                         break;
463                     }
464                 }
465             }
466             else
467             {
468                 char * digitptr = memchr( _PDCLIB_digits, tolower( rc ), status->base );
469                 if ( digitptr == NULL )
470                 {
471                     /* end of input item */
472                     UNGET( rc, status );
473                     break;
474                 }
475                 value *= status->base;
476                 value += digitptr - _PDCLIB_digits;
477                 value_parsed = true;
478             }
479         }
480         /* width or input exhausted, or non-matching character */
481         if ( ! value_parsed )
482         {
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 ) )
486             {
487                 status->n = -1;
488             }
489             return NULL;
490         }
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 |
494                                    E_unsigned ) )
495         {
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 ); */
512             default:
513                 puts( "UNSUPPORTED SCANF FLAG COMBINATION" );
514                 return NULL; /* behaviour unspecified */
515         }
516         ++(status->n);
517         return ++spec;
518     }
519     /* TODO: Floats. */
520     return NULL;
521 }
522
523
524 #ifdef TEST
525 #include <_PDCLIB_test.h>
526 #include <limits.h>
527
528
529  
530 int main( void )
531 {
532     /* Testing covered by fscanf.c */
533     return TEST_RESULTS;
534 }
535
536 #endif