Catching the unimplemented.
[fw/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 #define ASSIGN( case_cond, type ) \
35     case case_cond: \
36         *( va_arg( status->arg, type * ) ) = (type)( value * sign ); \
37         break
38
39
40 static int GET( struct _PDCLIB_status_t * status )
41 {
42     ++(status->i);
43     ++(status->this);
44     if ( status->stream != NULL )
45     {
46         return getc( status->stream );
47     }
48     else
49     {
50         return *((status->s)++);
51     }
52 }
53
54
55 static void UNGET( int c, struct _PDCLIB_status_t * status )
56 {
57     if ( status->stream != NULL )
58     {
59         ungetc( c, status->stream ); /* TODO: Error? */
60     }
61     else
62     {
63         *(--(status->s)) = c;
64     }
65     --(status->i);
66     --(status->this);
67 }
68
69
70 const char * _PDCLIB_scan( const char * spec, struct _PDCLIB_status_t * status )
71 {
72     /* generic input character */
73     int rc;
74     const char * orig_spec = spec;
75     if ( *(++spec) == '%' )
76     {
77         /* %% -> match single '%' */
78         rc = GET( status );
79         switch ( rc )
80         {
81             case EOF:
82                 /* matching failure */
83                 return NULL;
84             case '%':
85                 return ++spec;
86             default:
87                 UNGET( rc, status );
88                 break;
89         }
90     }
91     /* Initializing status structure */
92     status->flags = 0;
93     status->base = -1;
94     status->this = 0;
95     status->width = 0;
96     status->prec = 0;
97
98     /* '*' suppresses assigning parsed value to variable */
99     if ( *spec == '*' )
100     {
101         status->flags |= E_suppressed;
102         ++spec;
103     }
104
105     /* If a width is given, strtol() will return its value. If not given,
106        strtol() will return zero. In both cases, endptr will point to the
107        rest of the conversion specifier - just what we need.
108     */
109     status->width = (int)strtol( spec, (char**)&spec, 10 );
110
111     /* Optional length modifier
112        We step one character ahead in any case, and step back only if we find
113        there has been no length modifier (or step ahead another character if it
114        has been "hh" or "ll").
115     */
116     switch ( *(spec++) )
117     {
118         case 'h':
119             if ( *spec == 'h' )
120             {
121                 /* hh -> char */
122                 status->flags |= E_char;
123                 ++spec;
124             }
125             else
126             {
127                 /* h -> short */
128                 status->flags |= E_short;
129             }
130             break;
131         case 'l':
132             if ( *spec == 'l' )
133             {
134                 /* ll -> long long */
135                 status->flags |= E_llong;
136                 ++spec;
137             }
138             else
139             {
140                 /* l -> long */
141                 status->flags |= E_long;
142             }
143             break;
144         case 'j':
145             /* j -> intmax_t, which might or might not be long long */
146             status->flags |= E_intmax;
147             break;
148         case 'z':
149             /* z -> size_t, which might or might not be unsigned int */
150             status->flags |= E_size;
151             break;
152         case 't':
153             /* t -> ptrdiff_t, which might or might not be long */
154             status->flags |= E_ptrdiff;
155             break;
156         case 'L':
157             /* L -> long double */
158             status->flags |= E_ldouble;
159             break;
160         default:
161             --spec;
162             break;
163     }
164
165     /* Conversion specifier */
166
167     /* whether valid input had been parsed */
168     bool value_parsed = false;
169
170     switch ( *spec )
171     {
172         case 'd':
173             status->base = 10;
174             break;
175         case 'i':
176             status->base = 0;
177             break;
178         case 'o':
179             status->base = 8;
180             status->flags |= E_unsigned;
181             break;
182         case 'u':
183             status->base = 10;
184             status->flags |= E_unsigned;
185             break;
186         case 'x':
187             status->base = 16;
188             status->flags |= E_unsigned;
189             break;
190         case 'f':
191         case 'F':
192         case 'e':
193         case 'E':
194         case 'g':
195         case 'G':
196         case 'a':
197         case 'A':
198             break;
199         case 'c':
200         {
201             char * c = va_arg( status->arg, char * );
202             if ( status->width == SIZE_MAX )
203             {
204                 status->width = 1;
205             }
206             while ( ( status->this < status->width ) &&
207                     ( ( rc = GET( status ) ) != EOF ) )
208             {
209                 *(c++) = rc;
210                 value_parsed = true;
211             }
212             return value_parsed ? spec : NULL;
213         }
214         case 's':
215         {
216             char * c = va_arg( status->arg, char * );
217             while ( ( status->this < status->width ) && 
218                     ( ( rc = GET( status ) ) != EOF ) )
219             {
220                 if ( isspace( rc ) )
221                 {
222                     if ( value_parsed )
223                     {
224                         *c = '\0';
225                         return spec;
226                     }
227                     else
228                     {
229                         --(status->this);
230                     }
231                 }
232                 else
233                 {
234                     value_parsed = true;
235                     *(c++) = rc;
236                 }
237             }
238             /* width or input exhausted */
239             if ( value_parsed )
240             {
241                 *c = '\0';
242                 return spec;
243             }
244             else
245             {
246                 return NULL;
247             }
248         }
249         case 'p':
250             status->base = 16;
251             status->flags |= E_unsigned;
252             break;
253         case 'n':
254         {
255             int * val = va_arg( status->arg, int * );
256             *val = status->i;
257             return ++spec;
258         }
259         default:
260             /* No conversion specifier. Bad conversion. */
261             return orig_spec;
262     }
263
264     if ( status->base != -1 )
265     {
266         /* integer conversion */
267         uintmax_t value = 0;
268         bool prefix_parsed = false;
269         int sign = 0;
270         while ( ( status->this < status->width ) &&
271                 ( ( rc = GET( status ) ) != EOF ) )
272         {
273             if ( ! sign )
274             {
275                 switch ( rc )
276                 {
277                     case '-':
278                         sign = -1;
279                         break;
280                     case '+':
281                         sign = 1;
282                         break;
283                     default:
284                         sign = 1;
285                         UNGET( rc, status );
286                         break;
287                 }
288             }
289             else if ( ! prefix_parsed )
290             {
291                 prefix_parsed = true;
292                 if ( rc != '0' )
293                 {
294                     if ( status->base == 0 )
295                     {
296                         status->base = 10;
297                     }
298                     UNGET( rc, status );
299                 }
300                 else
301                 {
302                     if ( ( status->this < status->width ) &&
303                          ( ( rc = GET( status ) ) != EOF ) )
304                     {
305                         if ( tolower( rc ) == 'x' )
306                         {
307                             if ( ( status->base == 0 ) ||
308                                  ( status->base == 16 ) )
309                             {
310                                 status->base = 16;
311                             }
312                             else
313                             {
314                                 UNGET( rc, status );
315                                 value_parsed = true;
316                             }
317                         }
318                         else
319                         {
320                             UNGET( rc, status );
321                             if ( status->base == 0 )
322                             {
323                                 status->base = 8;
324                             }
325                             value_parsed = true;
326                         }
327                     }
328                 }
329             }
330             else
331             {
332                 char * digitptr = memchr( _PDCLIB_digits, tolower( rc ), status->base );
333                 if ( digitptr == NULL )
334                 {
335                     /* end of input item */
336                     break;
337                 }
338                 value *= status->base;
339                 value += digitptr - _PDCLIB_digits;
340                 value_parsed = true;
341             }
342         }
343         /* width exceeded, EOF, read error, non-matching character */
344         if ( ! value_parsed )
345         {
346             /* matching error */
347             return NULL;
348         }
349         switch ( status->flags & ( E_char | E_short | E_long | E_llong |
350                                    E_intmax | E_size | E_ptrdiff |
351                                    E_unsigned ) )
352         {
353             ASSIGN( E_char, char );
354             ASSIGN( E_char | E_unsigned, unsigned char );
355             ASSIGN( E_short, short );
356             ASSIGN( E_short | E_unsigned, unsigned short );
357             ASSIGN( 0, int );
358             ASSIGN( E_unsigned, unsigned int );
359             ASSIGN( E_long, long );
360             ASSIGN( E_long | E_unsigned, unsigned long );
361             ASSIGN( E_llong, long long );
362             ASSIGN( E_llong | E_unsigned, unsigned long long );
363             ASSIGN( E_intmax, intmax_t );
364             ASSIGN( E_intmax | E_unsigned, uintmax_t );
365             ASSIGN( E_size, size_t );
366             /* ASSIGN( E_size | E_unsigned, unsigned size_t ); */
367             ASSIGN( E_ptrdiff, ptrdiff_t );
368             /* ASSIGN( E_ptrdiff | E_unsigned, unsigned ptrdiff_t ); */
369             default:
370                 puts( "UNSUPPORTED SCANF FLAG COMBINATION" );
371                 return NULL;
372         }
373         return spec;
374     }
375     /* TODO: Floats. */
376     return NULL;
377 }
378
379
380 #ifdef TEST
381 #include <_PDCLIB_test.h>
382
383 int main( void )
384 {
385     TESTCASE( NO_TESTDRIVER );
386     return TEST_RESULTS;
387 }
388
389 #endif