]> pd.if.org Git - pdclib/blob - functions/_PDCLIB/scan.c
82e99fa4eaccc062ba8815b334c10c5e721b92c3
[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 '[':
303             // TODO: SOLAR
304             break;
305         case 'p':
306             status->base = 16;
307             status->flags |= E_unsigned;
308             break;
309         case 'n':
310         {
311             int * val = va_arg( status->arg, int * );
312             *val = status->i;
313             return ++spec;
314         }
315         default:
316             /* No conversion specifier. Bad conversion. */
317             return orig_spec;
318     }
319
320     if ( status->base != -1 )
321     {
322         /* integer conversion */
323         uintmax_t value = 0;         /* absolute value read */
324         bool prefix_parsed = false;
325         int sign = 0;
326         while ( ( status->this < status->width ) &&
327                 ( ( rc = GET( status ) ) != EOF ) )
328         {
329             if ( isspace( rc ) )
330             {
331                 if ( sign )
332                 {
333                     /* matching sequence terminated by whitespace */
334                     UNGET( rc, status );
335                     break;
336                 }
337                 else
338                 {
339                     /* leading whitespace not counted against width */
340                     status->this--;
341                 }
342             }
343             else if ( ! sign )
344             {
345                 /* no sign parsed yet */
346                 switch ( rc )
347                 {
348                     case '-':
349                         sign = -1;
350                         break;
351                     case '+':
352                         sign = 1;
353                         break;
354                     default:
355                         /* not a sign; put back character */
356                         sign = 1;
357                         UNGET( rc, status );
358                         break;
359                 }
360             }
361             else if ( ! prefix_parsed )
362             {
363                 /* no prefix (0x... for hex, 0... for octal) parsed yet */
364                 prefix_parsed = true;
365                 if ( rc != '0' )
366                 {
367                     /* not a prefix; if base not yet set, set to decimal */
368                     if ( status->base == 0 )
369                     {
370                         status->base = 10;
371                     }
372                     UNGET( rc, status );
373                 }
374                 else
375                 {
376                     /* starts with zero, so it might be a prefix. */
377                     /* check what follows next (might be 0x...) */
378                     if ( ( status->this < status->width ) &&
379                          ( ( rc = GET( status ) ) != EOF ) )
380                     {
381                         if ( tolower( rc ) == 'x' )
382                         {
383                             /* 0x... would be prefix for hex base... */
384                             if ( ( status->base == 0 ) ||
385                                  ( status->base == 16 ) )
386                             {
387                                 status->base = 16;
388                             }
389                             else
390                             {
391                                 /* ...unless already set to other value */
392                                 UNGET( rc, status );
393                                 value_parsed = true;
394                             }
395                         }
396                         else
397                         {
398                             /* 0... but not 0x.... would be octal prefix */
399                             UNGET( rc, status );
400                             if ( status->base == 0 )
401                             {
402                                 status->base = 8;
403                             }
404                             /* in any case we have read a zero */
405                             value_parsed = true;
406                         }
407                     }
408                     else
409                     {
410                         /* failed to read beyond the initial zero */
411                         value_parsed = true;
412                         break;
413                     }
414                 }
415             }
416             else
417             {
418                 char * digitptr = memchr( _PDCLIB_digits, tolower( rc ), status->base );
419                 if ( digitptr == NULL )
420                 {
421                     /* end of input item */
422                     UNGET( rc, status );
423                     break;
424                 }
425                 value *= status->base;
426                 value += digitptr - _PDCLIB_digits;
427                 value_parsed = true;
428             }
429         }
430         /* width or input exhausted, or non-matching character */
431         if ( ! value_parsed )
432         {
433             /* out of input before anything could be parsed - input error */
434             /* FIXME: if first character does not match, value_parsed is not set - but it is NOT an input error */
435             if ( ( status->n == 0 ) && ( rc == EOF ) )
436             {
437                 status->n = -1;
438             }
439             return NULL;
440         }
441         /* convert value to target type and assign to parameter */
442         switch ( status->flags & ( E_char | E_short | E_long | E_llong |
443                                    E_intmax | E_size | E_ptrdiff |
444                                    E_unsigned ) )
445         {
446             ASSIGN_VALUE_TO( E_char, char );
447             ASSIGN_VALUE_TO( E_char | E_unsigned, unsigned char );
448             ASSIGN_VALUE_TO( E_short, short );
449             ASSIGN_VALUE_TO( E_short | E_unsigned, unsigned short );
450             ASSIGN_VALUE_TO( 0, int );
451             ASSIGN_VALUE_TO( E_unsigned, unsigned int );
452             ASSIGN_VALUE_TO( E_long, long );
453             ASSIGN_VALUE_TO( E_long | E_unsigned, unsigned long );
454             ASSIGN_VALUE_TO( E_llong, long long );
455             ASSIGN_VALUE_TO( E_llong | E_unsigned, unsigned long long );
456             ASSIGN_VALUE_TO( E_intmax, intmax_t );
457             ASSIGN_VALUE_TO( E_intmax | E_unsigned, uintmax_t );
458             ASSIGN_VALUE_TO( E_size, size_t );
459             /* ASSIGN_VALUE_TO( E_size | E_unsigned, unsigned size_t ); */
460             ASSIGN_VALUE_TO( E_ptrdiff, ptrdiff_t );
461             /* ASSIGN_VALUE_TO( E_ptrdiff | E_unsigned, unsigned ptrdiff_t ); */
462             default:
463                 puts( "UNSUPPORTED SCANF FLAG COMBINATION" );
464                 return NULL; /* behaviour unspecified */
465         }
466         ++(status->n);
467         return ++spec;
468     }
469     /* TODO: Floats. */
470     return NULL;
471 }
472
473
474 #ifdef TEST
475 #include <_PDCLIB_test.h>
476 #include <limits.h>
477
478
479  
480 int main( void )
481 {
482     /* Testing covered by fscanf.c */
483     return TEST_RESULTS;
484 }
485
486 #endif