]> pd.if.org Git - pdclib.old/blob - draft.c
Started _PDCLIB_printf.
[pdclib.old] / draft.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <stdarg.h>
4 #include <stdint.h>
5 #include <stdbool.h>
6 #include <ctype.h>
7 #include <assert.h>
8 #include <string.h>
9 #include <limits.h>
10 #include <stddef.h>
11
12 #undef TEST
13 #include </home/solar/src/pdclib/functions/_PDCLIB/digits.c>
14
15 /* Using an integer's bits as flags for both the conversion flags and length
16    modifiers.
17 */
18 #define E_minus    1<<0
19 #define E_plus     1<<1
20 #define E_alt      1<<2
21 #define E_space    1<<3
22 #define E_zero     1<<4
23 #define E_done     1<<5
24 #define E_char     1<<6
25 #define E_short    1<<7
26 #define E_long     1<<8
27 #define E_llong    1<<9
28 #define E_intmax   1<<10
29 #define E_size     1<<11
30 #define E_ptrdiff  1<<12
31 #define E_double   1<<13
32 #define E_lower    1<<14
33 #define E_unsigned 1<<15
34
35 struct status_t
36 {
37     int           base;  /* base to which the value shall be converted       */
38     int_fast16_t  flags; /* flags and length modifiers                       */
39     size_t        n;     /* maximum number of characters to be written       */
40     size_t        i;     /* number of characters already written             */
41     size_t        this;  /* number of output chars in the current conversion */
42     char *        s;     /* target buffer                                    */
43     size_t        width; /* width of current field                           */
44     size_t        prec;  /* precision of current field                       */
45     FILE *        stream;/* for to-stream output                             */
46 };
47
48 const char * parse_out( const char * spec, struct status_t * status, va_list ap );
49 const char * parse_out_wrapper( const char * spec, struct status_t * status, ... );
50 int _PDCLIB_printf( FILE * stream, const char * format, va_list ap );
51
52 #define TESTCASE( _n, _value, _expect ) \
53     status.n = _n; \
54     status.i = 0; \
55     memset( status.s, '\0', 50 ); \
56     spec = _expect; \
57     ++spec; \
58     parse_out_wrapper( spec, &status, _value ); \
59     rc = snprintf( buffer, _n, _expect, _value ); \
60     if ( ( strcmp( status.s, buffer ) != 0 ) || ( status.i != rc ) ) \
61 { \
62         printf( "Output '%s', RC %d\nExpect '%s', RC %d\n\n", status.s, status.i, buffer, rc ); \
63 }
64
65 int main( void )
66 {
67     struct status_t status;
68     int rc;
69     char * buffer = malloc( 50 );
70     const char * spec;
71     status.s = calloc( 50, 1 );
72     status.i = 0;
73     status.stream = NULL;
74     status.n = SIZE_MAX;
75     puts( "- Signed min / max -\n" );
76     TESTCASE( SIZE_MAX, CHAR_MIN, "%hhd" );
77     TESTCASE( SIZE_MAX, CHAR_MAX, "%hhd" );
78     TESTCASE( SIZE_MAX, 0, "%hhd" );
79     TESTCASE( SIZE_MAX, SHRT_MIN, "%hd" );
80     TESTCASE( SIZE_MAX, SHRT_MAX, "%hd" );
81     TESTCASE( SIZE_MAX, 0, "%hd" );
82     TESTCASE( SIZE_MAX, INT_MIN, "%d" );
83     TESTCASE( SIZE_MAX, INT_MAX, "%d" );
84     TESTCASE( SIZE_MAX, 0, "%d" );
85     TESTCASE( SIZE_MAX, LONG_MIN, "%ld" );
86     TESTCASE( SIZE_MAX, LONG_MAX, "%ld" );
87     TESTCASE( SIZE_MAX, 0l, "%ld" );
88     TESTCASE( SIZE_MAX, LLONG_MIN, "%lld" );
89     TESTCASE( SIZE_MAX, LLONG_MAX, "%lld" );
90     TESTCASE( SIZE_MAX, 0ll, "%lld" ); 
91     puts( "- Unsigned min / max -\n" );
92     TESTCASE( SIZE_MAX, UCHAR_MAX, "%hhu" );
93     TESTCASE( SIZE_MAX, (unsigned char)-1, "%hhu" );
94     TESTCASE( SIZE_MAX, USHRT_MAX, "%hu" );
95     TESTCASE( SIZE_MAX, (unsigned short)-1, "%hu" );
96     TESTCASE( SIZE_MAX, UINT_MAX, "%u" );
97     TESTCASE( SIZE_MAX, -1u, "%u" );
98     TESTCASE( SIZE_MAX, ULONG_MAX, "%lu" );
99     TESTCASE( SIZE_MAX, -1ul, "%lu" );
100     TESTCASE( SIZE_MAX, ULLONG_MAX, "%llu" );
101     TESTCASE( SIZE_MAX, -1ull, "%llu" );
102     puts( "- Hex and Octal, normal and alternative, upper and lowercase -\n" );
103     TESTCASE( SIZE_MAX, UINT_MAX, "%X" );
104     TESTCASE( SIZE_MAX, -1u, "%#X" );
105     TESTCASE( SIZE_MAX, UINT_MAX, "%x" );
106     TESTCASE( SIZE_MAX, -1u, "%#x" );
107     TESTCASE( SIZE_MAX, UINT_MAX, "%o" );
108     TESTCASE( SIZE_MAX, -1u, "%#o" );
109     puts( "- Plus flag -\n" );
110     TESTCASE( SIZE_MAX, INT_MIN, "%+d" );
111     TESTCASE( SIZE_MAX, INT_MAX, "%+d" );
112     TESTCASE( SIZE_MAX, 0, "%+d" );
113     TESTCASE( SIZE_MAX, UINT_MAX, "%+u" );
114     TESTCASE( SIZE_MAX, -1u, "%+u" );
115     puts( "- Space flag -\n" );
116     TESTCASE( SIZE_MAX, INT_MIN, "% d" );
117     TESTCASE( SIZE_MAX, INT_MAX, "% d" );
118     TESTCASE( SIZE_MAX, 0, "% d" );
119     TESTCASE( SIZE_MAX, UINT_MAX, "% u" );
120     TESTCASE( SIZE_MAX, -1u, "% u" );
121     puts( "- Field width -\n" );
122     TESTCASE( SIZE_MAX, INT_MIN, "%9d" );
123     TESTCASE( SIZE_MAX, INT_MAX, "%9d" );
124     TESTCASE( SIZE_MAX, INT_MIN, "%10d" );
125     TESTCASE( SIZE_MAX, INT_MAX, "%10d" );
126     TESTCASE( SIZE_MAX, INT_MIN, "%11d" );
127     TESTCASE( SIZE_MAX, INT_MAX, "%11d" );
128     TESTCASE( SIZE_MAX, INT_MIN, "%12d" );
129     TESTCASE( SIZE_MAX, INT_MAX, "%12d" );
130     puts( "- Field width (left bound) -\n" );
131     TESTCASE( SIZE_MAX, INT_MIN, "%-9d" );
132     TESTCASE( SIZE_MAX, INT_MAX, "%-9d" );
133     TESTCASE( SIZE_MAX, INT_MIN, "%-10d" );
134     TESTCASE( SIZE_MAX, INT_MAX, "%-10d" );
135     TESTCASE( SIZE_MAX, INT_MIN, "%-11d" );
136     TESTCASE( SIZE_MAX, INT_MAX, "%-11d" );
137     TESTCASE( SIZE_MAX, INT_MIN, "%-12d" );
138     TESTCASE( SIZE_MAX, INT_MAX, "%-12d" );
139     puts( "- Field width, zero padding -\n");
140     TESTCASE( SIZE_MAX, INT_MIN, "%09d" );
141     TESTCASE( SIZE_MAX, INT_MAX, "%09d" );
142     TESTCASE( SIZE_MAX, INT_MIN, "%010d" );
143     TESTCASE( SIZE_MAX, INT_MAX, "%010d" );
144     TESTCASE( SIZE_MAX, INT_MIN, "%011d" );
145     TESTCASE( SIZE_MAX, INT_MAX, "%011d" );
146     TESTCASE( SIZE_MAX, INT_MIN, "%012d" );
147     TESTCASE( SIZE_MAX, INT_MAX, "%012d" );
148     puts( "- Field width, zero padding (left bound) -\n" );
149     TESTCASE( SIZE_MAX, INT_MIN, "%-09d" );
150     TESTCASE( SIZE_MAX, INT_MAX, "%-09d" );
151     TESTCASE( SIZE_MAX, INT_MIN, "%-010d" );
152     TESTCASE( SIZE_MAX, INT_MAX, "%-010d" );
153     TESTCASE( SIZE_MAX, INT_MIN, "%-011d" );
154     TESTCASE( SIZE_MAX, INT_MAX, "%-011d" );
155     TESTCASE( SIZE_MAX, INT_MIN, "%-012d" );
156     TESTCASE( SIZE_MAX, INT_MAX, "%-012d" );
157     return 0;
158 }
159
160 /* x - the character to be delivered
161    i - pointer to number of characters already delivered in this call
162    n - pointer to maximum number of characters to be delivered in this call
163    s - the buffer into which the character shall be delivered
164    TODO: Overruns.
165 */
166 #define DELIVER( x ) do { if ( status->i < status->n ) { if ( status->stream != NULL ) putc( x, status->stream ); else status->s[status->i] = x; } ++(status->i); } while ( 0 )
167
168 static void int2base( intmax_t value, struct status_t * status )
169 {
170     ++(status->this);
171     if ( ( value / status->base ) != 0 )
172     {
173         int2base( value / status->base, status );
174     }
175     else
176     {
177         char preface[3] = "\0\0";
178         size_t preidx = 0;
179         if ( ( status->flags & E_alt ) && ( status->base == 16 || status->base == 8 ) )
180         {
181             preface[ preidx++ ] = '0';
182             if ( status->base == 16 )
183             {
184                 preface[ preidx++ ] = ( status->flags & E_lower ) ? 'x' : 'X';
185             }
186         }
187         if ( value < 0 )
188         {
189             preface[ preidx++ ] = '-';
190         }
191         else if ( ! ( status->flags & E_unsigned ) )
192         {
193             if ( status->flags & E_plus )
194             {
195                 preface[ preidx++ ] = '+';
196             }
197             else if ( status->flags & E_space )
198             {
199                 preface[ preidx++ ] = ' ';
200             }
201         }
202         if ( ! ( ( status->flags & E_minus ) || ( status->flags & E_zero ) ) )
203         {
204             while ( ( status->this + preidx ) < status->width )
205             {
206                 DELIVER( ' ' );
207                 ++(status->this);
208             }
209         }
210         preidx = 0;
211         while ( preface[ preidx ] != '\0' )
212         {
213             DELIVER( preface[ preidx++ ] );
214             ++(status->this);
215         }
216         if ( ( ! ( status->flags & E_minus ) ) && ( status->flags & E_zero ) )
217         {
218             while ( status->this < status->width )
219             {
220                 DELIVER( '0' );
221                 ++(status->this);
222             }
223         }
224     }
225     {
226     int digit = value % status->base;
227     if ( digit < 0 )
228     {
229         digit *= -1;
230     }
231     if ( status->flags & E_lower )
232     {
233         DELIVER( _PDCLIB_digits[ digit ] );
234     }
235     else
236     {
237         DELIVER( toupper( _PDCLIB_digits[ digit ] ) );
238     }
239     }
240 }
241
242 const char * parse_out( const char * spec, struct status_t * status, va_list ap )
243 {
244     const char * orig_spec = spec;
245 #if 0
246     if ( *(++spec) == '%' )
247     {
248         DELIVER( *spec );
249         return spec;
250     }
251 #endif
252     /* Initializing status structure */
253     status->flags = 0;
254     status->base = 0;
255     status->this = 0;
256     status->width = 0;
257     status->prec = 0;
258
259     /* First come 0..n flags */
260     do
261     {
262         switch ( *spec )
263         {
264             case '-':
265                 status->flags |= E_minus;
266                 ++spec;
267                 break;
268             case '+':
269                 status->flags |= E_plus;
270                 ++spec;
271                 break;
272             case '#':
273                 status->flags |= E_alt;
274                 ++spec;
275                 break;
276             case ' ':
277                 status->flags |= E_space;
278                 ++spec;
279                 break;
280             case '0':
281                 status->flags |= E_zero;
282                 ++spec;
283                 break;
284             default:
285                 status->flags |= E_done;
286                 break;
287         }
288     } while ( ! ( status->flags & E_done ) );
289
290     /* Optional field width */
291     if ( *spec == '*' )
292     {
293         /* Retrieve width value from argument stack */
294         if ( ( status->width = va_arg( ap, int ) ) < 0 )
295         {
296             /* Negative value is '-' flag plus absolute value */
297             status->flags |= E_minus;
298             status->width *= -1;
299         }
300         ++spec;
301     }
302     else
303     {
304         /* If a width is given, strtol() will return its value. If not given,
305            strtol() will return zero. In both cases, endptr will point to the
306            rest of the conversion specifier - just what we need.
307         */
308         status->width = (int)strtol( spec, (char**)&spec, 10 );
309     }
310
311     /* Optional precision */
312     if ( *spec == '.' )
313     {
314         ++spec;
315         if ( *spec == '*' )
316         {
317             /* Retrieve precision value from argument stack. A negative value
318                is as if no precision is given - as precision is initalized to
319                EOF (negative), there is no need for testing for negative here.
320             */
321             status->prec = va_arg( ap, int );
322         }
323         else
324         {
325             char * endptr;
326             status->prec = (int)strtol( spec, &endptr, 10 );
327             if ( spec == endptr )
328             {
329                 /* TODO: Decimal point but no number - bad conversion specifier. */
330             }
331         }
332     }
333
334     /* Optional length modifier
335        We step one character ahead in any case, and step back only if we find
336        there has been no length modifier (or step ahead another character if it
337        has been "hh" or "ll").
338     */
339     switch ( *(spec++) )
340     {
341         case 'h':
342             if ( *spec == 'h' )
343             {
344                 status->flags |= E_char;
345                 ++spec;
346             }
347             else
348             {
349                 status->flags |= E_short;
350             }
351             break;
352         case 'l':
353             if ( *spec == 'l' )
354             {
355                 status->flags |= E_llong;
356                 ++spec;
357             }
358             else
359             {
360                 status->flags |= E_long;
361             }
362             break;
363         case 'j':
364             status->flags |= E_intmax;
365             break;
366         case 'z':
367             status->flags |= E_size;
368             break;
369         case 't':
370             status->flags |= E_ptrdiff;
371             break;
372         case 'L':
373             status->flags |= E_double;
374             break;
375         default:
376             --spec;
377             break;
378     }
379
380     /* Conversion specifier */
381     switch ( *spec )
382     {
383         case 'd':
384         case 'i':
385             status->base = 10;
386             break;
387         case 'o':
388             status->base = 8;
389             status->flags |= E_unsigned;
390             break;
391         case 'u':
392             status->base = 10;
393             status->flags |= E_unsigned;
394             break;
395         case 'x':
396             status->base = 16;
397             status->flags |= ( E_lower | E_unsigned );
398             break;
399         case 'X':
400             status->base = 16;
401             status->flags |= E_unsigned;
402             break;
403         case 'f':
404         case 'F':
405         case 'e':
406         case 'E':
407         case 'g':
408         case 'G':
409             break;
410         case 'a':
411         case 'A':
412             break;
413         case 'c':
414             break;
415         case 's':
416             break;
417         case 'p':
418             /* uint2base( 16, (intptr_t)value, true ) */
419         case 'n':
420             break;
421         default:
422             /* No conversion specifier. Bad conversion. */
423             return orig_spec;
424     }
425
426     /* Do the actual output based on our findings */
427     if ( status->base != 0 )
428     {
429         /* Integer conversions */
430         /* TODO: Check for invalid flag combinations. */
431         if ( status->flags & E_unsigned )
432         {
433             uintmax_t value;
434             switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_size ) )
435             {
436                 case E_char:
437                     value = (uintmax_t)(unsigned char)va_arg( ap, int );
438                     break;
439                 case E_short:
440                     value = (uintmax_t)(unsigned short)va_arg( ap, int );
441                     break;
442                 case 0:
443                     value = (uintmax_t)va_arg( ap, unsigned int );
444                     break;
445                 case E_long:
446                     value = (uintmax_t)va_arg( ap, unsigned long );
447                     break;
448                 case E_llong:
449                     value = (uintmax_t)va_arg( ap, unsigned long long );
450                     break;
451                 case E_size:
452                     value = (uintmax_t)va_arg( ap, size_t );
453                     break;
454             }
455             ++(status->this);
456             if ( ( value / status->base ) != 0 )
457             {
458                 int2base( (intmax_t)(value / status->base), status );
459             }
460             int digit = value % status->base;
461             if ( digit < 0 )
462             {
463                 digit *= -1;
464             }
465             if ( status->flags & E_lower )
466             {
467                 DELIVER( _PDCLIB_digits[ digit ] );
468             }
469             else
470             {
471                 DELIVER( toupper( _PDCLIB_digits[ digit ] ) );
472             }
473         }
474         else
475         {
476             switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_intmax ) )
477             {
478                 case E_char:
479                     int2base( (intmax_t)(char)va_arg( ap, int ), status );
480                     break;
481                 case E_short:
482                     int2base( (intmax_t)(short)va_arg( ap, int ), status );
483                     break;
484                 case 0:
485                     int2base( (intmax_t)va_arg( ap, int ), status );
486                     break;
487                 case E_long:
488                     int2base( (intmax_t)va_arg( ap, long ), status );
489                     break;
490                 case E_llong:
491                     int2base( (intmax_t)va_arg( ap, long long ), status );
492                     break;
493                 case E_ptrdiff:
494                     int2base( (intmax_t)va_arg( ap, ptrdiff_t ), status );
495                     break;
496                 case E_intmax:
497                     int2base( va_arg( ap, intmax_t ), status );
498                     break;
499             }
500         }
501         if ( status->flags & E_minus )
502         {
503             while ( status->this < status->width )
504             {
505                 DELIVER( ' ' );
506                 ++(status->this);
507             }
508         }
509         if ( status->i >= status->n )
510         {
511             status->s[status->n - 1] = '\0';
512         }
513     }
514     return ++spec;
515 }
516
517 const char * parse_out_wrapper( const char * spec, struct status_t * status, ... )
518 {
519     const char * rc;
520     va_list ap;
521     va_start( ap, status );
522     rc = parse_out( spec, status, ap );
523     va_end( ap );
524     return rc;
525 }
526
527 int _PDCLIB_printf( FILE * stream, const char * format, va_list ap )
528 {
529     char * buffer = malloc( 50 );
530     struct status_t status = { 0, 0, SIZE_MAX, 0, 0, /* stream->buffer */ buffer, 0, 0, stream };
531     while ( *format != '\0' )
532     {
533         const char * rc;
534         if ( ( *format != '%' ) || ( ( rc = parse_out( format, &status, ap ) ) == format ) )
535         {
536             /* No conversion specifier, print verbatim */
537             putc( *(format++), stream );
538         }
539         else
540         {
541             /* Continue parsing after conversion specifier */
542             format = rc;
543         }
544     }
545     return status.i;
546 }