]> pd.if.org Git - pdclib/blob - draft.c
Don't quote me, but this does look good...
[pdclib] / draft.c
1 #include <stdarg.h>
2 #include <stddef.h>
3 #include <stdint.h>
4 #include <stdio.h>
5 #include <stdlib.h>
6
7 /* These can be removed once integrated into PDCLIB make procedure */
8 #undef TEST
9 #include </home/solar/src/pdclib/functions/_PDCLIB/digits.c>
10 #include </home/solar/src/pdclib/functions/_PDCLIB/Xdigits.c>
11
12 /* Using an integer's bits as flags for both the conversion flags and length
13    modifiers.
14 */
15 #define E_minus    1<<0
16 #define E_plus     1<<1
17 #define E_alt      1<<2
18 #define E_space    1<<3
19 #define E_zero     1<<4
20 #define E_done     1<<5
21 #define E_char     1<<6
22 #define E_short    1<<7
23 #define E_long     1<<8
24 #define E_llong    1<<9
25 #define E_intmax   1<<10
26 #define E_size     1<<11
27 #define E_ptrdiff  1<<12
28 #define E_double   1<<13
29 #define E_lower    1<<14
30 #define E_unsigned 1<<15
31
32 struct status_t
33 {
34     int           base;  /* base to which the value shall be converted       */
35     int_fast32_t  flags; /* flags and length modifiers                       */
36     size_t        n;     /* maximum number of characters to be written       */
37     size_t        i;     /* number of characters already written             */
38     size_t        this;  /* number of output chars in the current conversion */
39     char *        s;     /* target buffer                                    */
40     size_t        width; /* width of current field                           */
41     size_t        prec;  /* precision of current field                       */
42     FILE *        stream;/* for to-stream output                             */
43     va_list       ap;    /* the argument stack passed to the printf function */
44 };
45
46 const char * parse_out( const char * spec, struct status_t * status );
47 inline void test( size_t n, const char * expect, ... );
48 int _PDCLIB_sprintf( char * buffer, size_t n, const char * format, va_list ap );
49
50 /* The following only for testing. */
51 #include <limits.h>
52 #include <string.h>
53
54 int main( void )
55 {
56     puts( "- Signed min / max -\n" );
57     test( SIZE_MAX, "%hhd", CHAR_MIN );
58     test( SIZE_MAX, "%hhd", CHAR_MAX );
59     test( SIZE_MAX, "%hhd", 0 );
60     test( SIZE_MAX, "%hd", SHRT_MIN );
61     test( SIZE_MAX, "%hd", SHRT_MAX );
62     test( SIZE_MAX, "%hd", 0 );
63     test( SIZE_MAX, "%d", INT_MIN );
64     test( SIZE_MAX, "%d", INT_MAX );
65     test( SIZE_MAX, "%d", 0 );
66     test( SIZE_MAX, "%ld", LONG_MIN );
67     test( SIZE_MAX, "%ld", LONG_MAX );
68     test( SIZE_MAX, "%ld", 0l );
69     test( SIZE_MAX, "%lld", LLONG_MIN );
70     test( SIZE_MAX, "%lld", LLONG_MAX );
71     test( SIZE_MAX, "%lld", 0ll );
72     puts( "- Unsigned min / max -\n" );
73     test( SIZE_MAX, "%hhu", UCHAR_MAX );
74     test( SIZE_MAX, "%hhu", (unsigned char)-1 );
75     test( SIZE_MAX, "%hu", USHRT_MAX );
76     test( SIZE_MAX, "%hu", (unsigned short)-1 );
77     test( SIZE_MAX, "%u", UINT_MAX );
78     test( SIZE_MAX, "%u", -1u );
79     test( SIZE_MAX, "%lu", ULONG_MAX );
80     test( SIZE_MAX, "%lu", -1ul );
81     test( SIZE_MAX, "%llu", ULLONG_MAX );
82     test( SIZE_MAX, "%llu", -1ull );
83     puts( "- Hex and Octal, normal and alternative, upper and lowercase -\n" );
84     test( SIZE_MAX, "%X", UINT_MAX );
85     test( SIZE_MAX, "%#X", -1u );
86     test( SIZE_MAX, "%x", UINT_MAX );
87     test( SIZE_MAX, "%#x", -1u );
88     test( SIZE_MAX, "%o", UINT_MAX );
89     test( SIZE_MAX, "%#o", -1u );
90     puts( "- Plus flag -\n" );
91     test( SIZE_MAX, "%+d", INT_MIN );
92     test( SIZE_MAX, "%+d", INT_MAX );
93     test( SIZE_MAX, "%+d", 0 );
94     test( SIZE_MAX, "%+u", UINT_MAX );
95     test( SIZE_MAX, "%+u", -1u );
96     puts( "- Space flag -\n" );
97     test( SIZE_MAX, "% d", INT_MIN );
98     test( SIZE_MAX, "% d", INT_MAX );
99     test( SIZE_MAX, "% d", 0 );
100     test( SIZE_MAX, "% u", UINT_MAX );
101     test( SIZE_MAX, "% u", -1u );
102     puts( "- Field width -\n" );
103     test( SIZE_MAX, "%9d", INT_MIN );
104     test( SIZE_MAX, "%9d", INT_MAX );
105     test( SIZE_MAX, "%10d", INT_MIN );
106     test( SIZE_MAX, "%10d", INT_MAX );
107     test( SIZE_MAX, "%11d", INT_MIN );
108     test( SIZE_MAX, "%11d", INT_MAX );
109     test( SIZE_MAX, "%12d", INT_MIN );
110     test( SIZE_MAX, "%12d", INT_MAX );
111     puts( "- Field width (left bound) -\n" );
112     test( SIZE_MAX, "%-9d", INT_MIN );
113     test( SIZE_MAX, "%-9d", INT_MAX );
114     test( SIZE_MAX, "%-10d", INT_MIN );
115     test( SIZE_MAX, "%-10d", INT_MAX );
116     test( SIZE_MAX, "%-11d", INT_MIN );
117     test( SIZE_MAX, "%-11d", INT_MAX );
118     test( SIZE_MAX, "%-12d", INT_MIN );
119     test( SIZE_MAX, "%-12d", INT_MAX );
120     puts( "- Field width, zero padding -\n");
121     test( SIZE_MAX, "%09d", INT_MIN );
122     test( SIZE_MAX, "%09d", INT_MAX );
123     test( SIZE_MAX, "%010d", INT_MIN );
124     test( SIZE_MAX, "%010d", INT_MAX );
125     test( SIZE_MAX, "%011d", INT_MIN );
126     test( SIZE_MAX, "%011d", INT_MAX );
127     test( SIZE_MAX, "%012d", INT_MIN );
128     test( SIZE_MAX, "%012d", INT_MAX );
129     puts( "- Field width, zero padding (left bound) -\n" );
130     test( SIZE_MAX, "%-09d", INT_MIN );
131     test( SIZE_MAX, "%-09d", INT_MAX );
132     test( SIZE_MAX, "%-010d", INT_MIN );
133     test( SIZE_MAX, "%-010d", INT_MAX );
134     test( SIZE_MAX, "%-011d", INT_MIN );
135     test( SIZE_MAX, "%-011d", INT_MAX );
136     test( SIZE_MAX, "%-012d", INT_MIN );
137     test( SIZE_MAX, "%-012d", INT_MAX );
138     puts( "- Limited n -\n" );
139     test( 8, "%9d", INT_MAX );
140     test( 8, "%9d", INT_MIN );
141     test( 9, "%9d", INT_MAX );
142     test( 9, "%9d", INT_MIN );
143     test( 10, "%9d", INT_MAX );
144     test( 10, "%9d", INT_MIN );
145     test( 9, "%10d", INT_MAX );
146     test( 9, "%10d", INT_MIN );
147     test( 10, "%10d", INT_MAX );
148     test( 10, "%10d", INT_MIN );
149     test( 11, "%10d", INT_MAX );
150     test( 11, "%10d", INT_MIN );
151     test( 10, "%11d", INT_MAX );
152     test( 10, "%11d", INT_MIN );
153     test( 11, "%11d", INT_MAX );
154     test( 11, "%11d", INT_MIN );
155     test( 12, "%11d", INT_MAX );
156     test( 12, "%11d", INT_MIN );
157     test( 11, "%12d", INT_MAX );
158     test( 11, "%12d", INT_MIN );
159     test( 12, "%12d", INT_MAX );
160     test( 12, "%12d", INT_MIN );
161     test( 13, "%12d", INT_MAX );
162     test( 13, "%12d", INT_MIN );
163     puts( "- Precision -\n" );
164     test( SIZE_MAX, "%030.20d", INT_MAX );
165     test( SIZE_MAX, "%.6x", UINT_MAX );
166     test( SIZE_MAX, "%#6.3x", UINT_MAX );
167     test( SIZE_MAX, "%#3.6x", UINT_MAX );
168     test( SIZE_MAX, "%.6d", INT_MIN );
169     test( SIZE_MAX, "%6.3d", INT_MIN );
170     test( SIZE_MAX, "%3.6d", INT_MIN );
171     test( SIZE_MAX, "%#0.6x", UINT_MAX );
172     test( SIZE_MAX, "%#06.3x", UINT_MAX );
173     test( SIZE_MAX, "%#03.6x", UINT_MAX );
174     test( SIZE_MAX, "%#0.6d", INT_MAX );
175     test( SIZE_MAX, "%#06.3d", INT_MAX );
176     test( SIZE_MAX, "%#03.6d", INT_MAX );
177     test( SIZE_MAX, "%#+.6d", INT_MAX );
178     test( SIZE_MAX, "%#+6.3d", INT_MAX );
179     test( SIZE_MAX, "%#+3.6d", INT_MAX );
180     test( SIZE_MAX, "%+0.6d", INT_MAX );
181     test( SIZE_MAX, "%+06.3d", INT_MAX );
182     test( SIZE_MAX, "%+03.6d", INT_MAX );
183     return 0;
184 }
185
186 /* This macro delivers a given character to either a memory buffer or a stream,
187    depending on the contents of 'status' (struct status_t).
188    x - the character to be delivered
189    i - pointer to number of characters already delivered in this call
190    n - pointer to maximum number of characters to be delivered in this call
191    s - the buffer into which the character shall be delivered
192    TODO: Overruns.
193 */
194 #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 )
195
196 /* This function recursively converts a given integer value to a given base
197    into a character string. Persistent information - like the number of digits
198    parsed so far - is recorded in a struct status_t, which allows to avoid
199    overwriting snprintf() limits, and enables the function to do the necessary
200    padding / prefixing of the character string eventually printed.
201 */
202 static void int2base( intmax_t value, struct status_t * status )
203 {
204     /* Registering the character being printed at the end of the function here
205        already so it will be taken into account when the deepestmost recursion
206        does the prefix / padding stuff.
207     */
208     ++(status->this);
209     if ( ( value / status->base ) != 0 )
210     {
211         /* More digits to be done - recurse deeper */
212         int2base( value / status->base, status );
213     }
214     else
215     {
216         /* We reached the last digit, the deepest point of our recursion, and
217            only now know how long the number to be printed actually is. Now we
218            have to do the sign, prefix, width, and precision padding stuff
219            before printing the numbers while we resurface from the recursion.
220         */
221         /* At worst, we need two prefix characters (hex prefix). */
222         char preface[3] = "\0";
223         size_t preidx = 0;
224         if ( ( status->flags & E_alt ) && ( status->base == 16 || status->base == 8 ) )
225         {
226             /* Octal / hexadecimal prefix for "%#" conversions */
227             preface[ preidx++ ] = '0'; /* TODO: For octal, standard states "extend the precision" */
228             if ( status->base == 16 )
229             {
230                 preface[ preidx++ ] = ( status->flags & E_lower ) ? 'x' : 'X';
231             }
232         }
233         if ( value < 0 )
234         {
235             /* Negative sign for negative values - at all times. */
236             preface[ preidx++ ] = '-';
237         }
238         else if ( ! ( status->flags & E_unsigned ) )
239         {
240             /* plus sign / extra space are only for unsigned conversions */
241             if ( status->flags & E_plus )
242             {
243                 preface[ preidx++ ] = '+';
244             }
245             else if ( status->flags & E_space )
246             {
247                 preface[ preidx++ ] = ' ';
248             }
249         }
250         {
251         size_t prec_pads = ( status->prec > status->this ) ? ( status->prec - status->this ) : 0;
252         if ( ! ( status->flags & ( E_minus | E_zero ) ) )
253         {
254             /* Space padding is only done if no zero padding or left alignment
255                is requested. Leave space for any prefixes determined above.
256             */
257             /* The number of characters to be printed, plus prefixes if any. */
258             /* This line contained probably the most stupid, time-wasting bug
259                I've ever perpetrated. Greetings to Samface, DevL, and all
260                sceners at Breakpoint 2006.
261             */
262             size_t characters = preidx + ( ( status->this > status->prec ) ? status->this : status->prec );
263             if ( status->width > characters )
264             {
265                 for ( int i = 0; i < status->width - characters; ++i )
266                 {
267                     DELIVER( ' ' );
268                     ++(status->this); /* TODO: Probably have to do something so I still know how many zeroes are required, later. */
269                 }
270             }
271         }
272         /* Now we did the padding, do the prefixes (if any). */
273         preidx = 0;
274         while ( preface[ preidx ] != '\0' )
275         {
276             DELIVER( preface[ preidx++ ] );
277             ++(status->this);
278         }
279         if ( ( ! ( status->flags & E_minus ) ) && ( status->flags & E_zero ) )
280         {
281             /* If field is not left aligned, and zero padding is requested, do
282                so. TODO: This should include precision handling (probably).
283             */
284             while ( status->this < status->width )
285             {
286                 DELIVER( '0' );
287                 ++(status->this);
288             }
289         }
290         for ( int i = 0; i < prec_pads; ++i )
291         {
292             DELIVER( '0' );
293         }
294         }
295     }
296     /* Recursion tail - print the current digit. */
297     {
298     int digit = value % status->base;
299     if ( digit < 0 )
300     {
301         digit *= -1;
302     }
303     if ( status->flags & E_lower )
304     {
305         /* Lowercase letters. Same array used for strto...(). */
306         DELIVER( _PDCLIB_digits[ digit ] );
307     }
308     else
309     {
310         /* Uppercase letters. Array only used here, only 0-F. */
311         DELIVER( _PDCLIB_Xdigits[ digit ] );
312     }
313     }
314 }
315
316 /* This function is to be called with spec pointing to the leading '%' of a
317    printf() conversion specifier, with ap being 
318 */
319 const char * parse_out( const char * spec, struct status_t * status )
320 {
321     const char * orig_spec = spec;
322     if ( *(++spec) == '%' )
323     {
324         DELIVER( *spec );
325         return spec;
326     }
327     /* Initializing status structure */
328     status->flags = 0;
329     status->base  = 0;
330     status->this  = 0;
331     status->width = 0;
332     status->prec  = 0;
333
334     /* First come 0..n flags */
335     do
336     {
337         switch ( *spec )
338         {
339             case '-':
340                 status->flags |= E_minus;
341                 ++spec;
342                 break;
343             case '+':
344                 status->flags |= E_plus;
345                 ++spec;
346                 break;
347             case '#':
348                 status->flags |= E_alt;
349                 ++spec;
350                 break;
351             case ' ':
352                 status->flags |= E_space;
353                 ++spec;
354                 break;
355             case '0':
356                 status->flags |= E_zero;
357                 ++spec;
358                 break;
359             default:
360                 status->flags |= E_done;
361                 break;
362         }
363     } while ( ! ( status->flags & E_done ) );
364
365     /* Optional field width */
366     if ( *spec == '*' )
367     {
368         /* Retrieve width value from argument stack */
369         if ( ( status->width = va_arg( status->ap, int ) ) < 0 )
370         {
371             /* Negative value is '-' flag plus absolute value */
372             status->flags |= E_minus;
373             status->width *= -1;
374         }
375         ++spec;
376     }
377     else
378     {
379         /* If a width is given, strtol() will return its value. If not given,
380            strtol() will return zero. In both cases, endptr will point to the
381            rest of the conversion specifier - just what we need.
382         */
383         status->width = (int)strtol( spec, (char**)&spec, 10 );
384     }
385
386     /* Optional precision */
387     if ( *spec == '.' )
388     {
389         ++spec;
390         if ( *spec == '*' )
391         {
392             /* Retrieve precision value from argument stack. A negative value
393                is as if no precision is given - as precision is initalized to
394                EOF (negative), there is no need for testing for negative here.
395             */
396             status->prec = va_arg( status->ap, int );
397         }
398         else
399         {
400             char * endptr;
401             status->prec = (int)strtol( spec, &endptr, 10 );
402             if ( spec == endptr )
403             {
404                 /* Decimal point but no number - bad conversion specifier. */
405                 return orig_spec;
406             }
407             spec = endptr;
408         }
409         /* Having a precision cancels out any zero flag. */
410         status->flags ^= E_zero;
411     }
412
413     /* Optional length modifier
414        We step one character ahead in any case, and step back only if we find
415        there has been no length modifier (or step ahead another character if it
416        has been "hh" or "ll").
417     */
418     switch ( *(spec++) )
419     {
420         case 'h':
421             if ( *spec == 'h' )
422             {
423                 status->flags |= E_char;
424                 ++spec;
425             }
426             else
427             {
428                 status->flags |= E_short;
429             }
430             break;
431         case 'l':
432             if ( *spec == 'l' )
433             {
434                 status->flags |= E_llong;
435                 ++spec;
436             }
437             else
438             {
439                 status->flags |= E_long;
440             }
441             break;
442         case 'j':
443             status->flags |= E_intmax;
444             break;
445         case 'z':
446             status->flags |= E_size;
447             break;
448         case 't':
449             status->flags |= E_ptrdiff;
450             break;
451         case 'L':
452             status->flags |= E_double;
453             break;
454         default:
455             --spec;
456             break;
457     }
458
459     /* Conversion specifier */
460     switch ( *spec )
461     {
462         case 'd':
463         case 'i':
464             status->base = 10;
465             break;
466         case 'o':
467             status->base = 8;
468             status->flags |= E_unsigned;
469             break;
470         case 'u':
471             status->base = 10;
472             status->flags |= E_unsigned;
473             break;
474         case 'x':
475             status->base = 16;
476             status->flags |= ( E_lower | E_unsigned );
477             break;
478         case 'X':
479             status->base = 16;
480             status->flags |= E_unsigned;
481             break;
482         case 'f':
483         case 'F':
484         case 'e':
485         case 'E':
486         case 'g':
487         case 'G':
488             break;
489         case 'a':
490         case 'A':
491             break;
492         case 'c':
493             break;
494         case 's':
495             break;
496         case 'p':
497             /* uint2base( 16, (intptr_t)value, true ) */
498         case 'n':
499             break;
500         default:
501             /* No conversion specifier. Bad conversion. */
502             return orig_spec;
503     }
504
505     /* Do the actual output based on our findings */
506     if ( status->base != 0 )
507     {
508         /* Integer conversions */
509         /* TODO: Check for invalid flag combinations. */
510         if ( status->flags & E_unsigned )
511         {
512             uintmax_t value;
513             switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_size ) )
514             {
515                 case E_char:
516                     value = (uintmax_t)(unsigned char)va_arg( status->ap, int );
517                     break;
518                 case E_short:
519                     value = (uintmax_t)(unsigned short)va_arg( status->ap, int );
520                     break;
521                 case 0:
522                     value = (uintmax_t)va_arg( status->ap, unsigned int );
523                     break;
524                 case E_long:
525                     value = (uintmax_t)va_arg( status->ap, unsigned long );
526                     break;
527                 case E_llong:
528                     value = (uintmax_t)va_arg( status->ap, unsigned long long );
529                     break;
530                 case E_size:
531                     value = (uintmax_t)va_arg( status->ap, size_t );
532                     break;
533             }
534             ++(status->this);
535             if ( ( value / status->base ) != 0 )
536             {
537                 int2base( (intmax_t)(value / status->base), status );
538             }
539             int digit = value % status->base;
540             if ( digit < 0 )
541             {
542                 digit *= -1;
543             }
544             if ( status->flags & E_lower )
545             {
546                 DELIVER( _PDCLIB_digits[ digit ] );
547             }
548             else
549             {
550                 DELIVER( _PDCLIB_Xdigits[ digit ] );
551             }
552         }
553         else
554         {
555             switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_intmax ) )
556             {
557                 case E_char:
558                     int2base( (intmax_t)(char)va_arg( status->ap, int ), status );
559                     break;
560                 case E_short:
561                     int2base( (intmax_t)(short)va_arg( status->ap, int ), status );
562                     break;
563                 case 0:
564                     int2base( (intmax_t)va_arg( status->ap, int ), status );
565                     break;
566                 case E_long:
567                     int2base( (intmax_t)va_arg( status->ap, long ), status );
568                     break;
569                 case E_llong:
570                     int2base( (intmax_t)va_arg( status->ap, long long ), status );
571                     break;
572                 case E_ptrdiff:
573                     int2base( (intmax_t)va_arg( status->ap, ptrdiff_t ), status );
574                     break;
575                 case E_intmax:
576                     int2base( va_arg( status->ap, intmax_t ), status );
577                     break;
578             }
579         }
580         if ( status->flags & E_minus )
581         {
582             while ( status->this < status->width )
583             {
584                 DELIVER( ' ' );
585                 ++(status->this);
586             }
587         }
588         if ( status->i >= status->n )
589         {
590             status->s[status->n - 1] = '\0';
591         }
592     }
593     return ++spec;
594 }
595
596 inline void test( size_t n, const char * expect, ... )
597 {
598     char * buffer1 = malloc( 50 );
599     char * buffer2 = malloc( 50 );
600     int myrc;
601     int rc;
602     va_list ap;
603     va_start( ap, expect );
604     myrc = _PDCLIB_sprintf( buffer1, n, expect, ap );
605     rc = vsnprintf( buffer2, n, expect, ap );
606     if ( ( strcmp( buffer1, buffer2 ) != 0 ) || ( myrc != rc ) )
607     {
608         printf( "Output '%s', RC %d\nExpect '%s', RC %d\n\n", buffer1, myrc, buffer2, rc );
609     }
610     free( buffer1 );
611     free( buffer2 );
612 }
613
614 int _PDCLIB_sprintf( char * buffer, size_t n, const char * format, va_list ap )
615 {
616     struct status_t status = { 0, 0, n, 0, 0, buffer, 0, 0, NULL, ap };
617     while ( *format != '\0' )
618     {
619         const char * rc;
620         if ( ( *format != '%' ) || ( ( rc = parse_out( format, &status ) ) == format ) )
621         {
622             /* No conversion specifier, print verbatim */
623             buffer[ status.i++ ] = *(format++);
624         }
625         else
626         {
627             /* Continue parsing after conversion specifier */
628             format = rc;
629         }
630     }
631     buffer[ status.i ] = '\0';
632     return status.i;
633 }
634
635 #if 0
636 int _PDCLIB_fprintf( FILE * stream, const char * format, va_list ap )
637 {
638     struct status_t status = { 0, 0, SIZE_MAX, 0, 0, NULL, 0, 0, stream, ap };
639     while ( *format != '\0' )
640     {
641         const char * rc;
642         if ( ( *format != '%' ) || ( ( rc = parse_out( format, &status, ap ) ) == format ) )
643         {
644             /* No conversion specifier, print verbatim */
645             putc( *(format++), stream );
646         }
647         else
648         {
649             /* Continue parsing after conversion specifier */
650             format = rc;
651         }
652     }
653     return status.i;
654 }
655 #endif