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