* support/regression/tests/snprintf.c: some checks
[fw/sdcc] / device / lib / printf_large.c
1 /*-------------------------------------------------------------------------
2   printf_large.c - formatted output conversion
3
4              Written By - Martijn van Balen aed@iae.nl (1999)
5              Added %f By - johan.knol@iduna.nl (2000)
6              Refactored by - Maarten Brock (2004)
7
8    This library is free software; you can redistribute it and/or
9    modify it under the terms of the GNU Lesser General Public
10    License as published by the Free Software Foundation; either
11    version 2.1 of the License, or (at your option) any later version.
12
13    This library is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16    Lesser General Public License for more details.
17
18    You should have received a copy of the GNU Lesser General Public
19    License along with this library; if not, write to the Free Software
20    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
21
22    In other words, you are welcome to use, share and improve this program.
23    You are forbidden to forbid anyone else to use, share and improve
24    what you give them.   Help stamp out software-hoarding!
25 -------------------------------------------------------------------------*/
26
27 #if defined (SDCC_ds390)
28 #define USE_FLOATS 1
29 #endif
30
31 #include <stdarg.h>
32 #include <string.h>
33 #include <ctype.h>
34 #include <stdio.h>
35 #include <stdbool.h>
36 #include <sdcc-lib.h>
37
38 #define PTR value.ptr
39
40 #ifdef SDCC_ds390
41 #define NULL_STRING "<NULL>"
42 #define NULL_STRING_LENGTH 6
43 #endif
44
45 /****************************************************************************/
46
47 //typedef char * ptr_t;
48 #define ptr_t char *
49
50 #ifdef toupper
51 #undef toupper
52 #endif
53 #ifdef tolower
54 #undef tolower
55 #endif
56 #ifdef islower
57 #undef islower
58 #endif
59 #ifdef isdigit
60 #undef isdigit
61 #endif
62
63 //#define toupper(c) ((c)&=~0x20)
64 #define toupper(c) ((c)&=0xDF)
65 #define tolower(c) ((c)|=0x20)
66 #define islower(c) ((unsigned char)c >= (unsigned char)'a' && (unsigned char)c <= (unsigned char)'z')
67 #define isdigit(c) ((unsigned char)c >= (unsigned char)'0' && (unsigned char)c <= (unsigned char)'9')
68
69 typedef union
70 {
71   unsigned char  byte[5];
72   long           l;
73   unsigned long  ul;
74   float          f;
75   char           *ptr;
76 } value_t;
77
78 #ifndef SDCC_STACK_AUTO
79   static BOOL lower_case;
80   static pfn_outputchar output_char;
81   static void* p;
82   static value_t value;
83   static int charsOutputted;
84 #endif
85
86 /****************************************************************************/
87
88 #ifdef SDCC_STACK_AUTO
89   #define OUTPUT_CHAR(c, p) { output_char (c, p); charsOutputted++; }
90 #else
91   #define OUTPUT_CHAR(c, p) _output_char (c)
92   static void _output_char( unsigned char c )
93   {
94     output_char( c, p );
95     charsOutputted++;
96   }
97 #endif
98
99 /*--------------------------------------------------------------------------*/
100
101 #ifdef SDCC_STACK_AUTO
102   static void output_digit( unsigned char n, BOOL lower_case, pfn_outputchar output_char, void* p )
103   {
104     register unsigned char c = n + (unsigned char)'0';
105
106     if (c > (unsigned char)'9')
107     {
108       c += (unsigned char)('A' - '0' - 10);
109       if (lower_case)
110          c += (unsigned char)('a' - 'A');
111     }
112     output_char( c, p );
113   }
114 #else
115   static void output_digit( unsigned char n )
116   {
117     register unsigned char c = n + (unsigned char)'0';
118
119     if (c > (unsigned char)'9')
120     {
121       c += (unsigned char)('A' - '0' - 10);
122       if (lower_case)
123          c = tolower(c);
124     }
125     _output_char( c );
126   }
127 #endif
128
129 /*--------------------------------------------------------------------------*/
130
131 #ifdef SDCC_STACK_AUTO
132   #define OUTPUT_2DIGITS( B )   { output_2digits( B, lower_case, output_char, p ); charsOutputted += 2; }
133   static void output_2digits( unsigned char b, BOOL lower_case, pfn_outputchar output_char, void* p )
134   {
135     output_digit( b>>4,   lower_case, output_char, p );
136     output_digit( b&0x0F, lower_case, output_char, p );
137   }
138 #else
139   #define OUTPUT_2DIGITS( B )   output_2digits( B )
140   static void output_2digits( unsigned char b )
141   {
142     output_digit( b>>4   );
143     output_digit( b&0x0F );
144   }
145 #endif
146
147 /*--------------------------------------------------------------------------*/
148
149 #if defined SDCC_STACK_AUTO
150 static void calculate_digit( value_t _AUTOMEM * value, unsigned char radix )
151 {
152   unsigned long ul = value->ul;
153   unsigned char _AUTOMEM * pb4 = &value->byte[4];
154   unsigned char i = 32;
155
156   do
157   {
158     *pb4 = (*pb4 << 1) | ((ul >> 31) & 0x01);
159     ul <<= 1;
160
161     if (radix <= *pb4 )
162     {
163       *pb4 -= radix;
164       ul |= 1;
165     }
166   } while (--i);
167   value->ul = ul;
168 }
169 #else
170 static void calculate_digit( unsigned char radix )
171 {
172   register unsigned long ul = value.ul;
173   register unsigned char b4 = value.byte[4];
174   register unsigned char i = 32;
175
176   do
177   {
178     b4 = (b4 << 1);
179     b4 |= (ul >> 31) & 0x01;
180     ul <<= 1;
181
182     if (radix <= b4 )
183     {
184       b4 -= radix;
185       ul |= 1;
186     }
187   } while (--i);
188   value.ul = ul;
189   value.byte[4] = b4;
190 }
191 #endif
192
193 #if USE_FLOATS
194
195 /* This is a very inefficient but direct approach, since we have no math
196    library yet (e.g. log()).
197    It does most of the modifiers, but has some restrictions. E.g. the
198    abs(float) shouldn't be bigger than an unsigned long (that's
199    about 4294967295), but still makes it usefull for most real-life
200    applications.
201 */
202
203 #define DEFAULT_FLOAT_PRECISION 6
204
205 #ifdef SDCC_STACK_AUTO
206 #define OUTPUT_FLOAT(F, W, D, L, Z, S, P)       output_float(F, W, D, L, Z, S, P, output_char, p)
207 static unsigned char
208 output_float (float f, unsigned char reqWidth,
209               signed char reqDecimals,
210               BOOL left, BOOL zero, BOOL sign, BOOL space,
211               pfn_outputchar output_char, void* p)
212 {
213   unsigned char charsOutputted = 0;
214 #else
215 #define OUTPUT_FLOAT(F, W, D, L, Z, S, P)       output_float(F, W, D, L, Z, S, P)
216 static void
217 output_float (float f, unsigned char reqWidth,
218               signed char reqDecimals,
219               BOOL left, BOOL zero, BOOL sign, BOOL space)
220 {
221 #endif //SDCC_STACK_AUTO
222   BOOL negative = 0;
223   unsigned long integerPart;
224   float decimalPart;
225   char fpBuffer[128];
226   char fpBI=0, fpBD;
227   unsigned char minWidth, i;
228
229   // save the sign
230   if (f<0) {
231     negative=1;
232     f=-f;
233   }
234
235   if (f>0x00ffffff) {
236     // this part is from Frank van der Hulst
237     signed char exp;
238
239     for (exp = 0; f >= 10.0; exp++) f /=10.0;
240     for (       ; f < 1.0;   exp--) f *=10.0;
241
242     if (negative) {
243       OUTPUT_CHAR ('-', p);
244     } else {
245       if (sign) {
246         OUTPUT_CHAR ('+', p);
247       }
248     }
249 #ifdef SDCC_STACK_AUTO
250     charsOutputted += OUTPUT_FLOAT(f, 0, reqDecimals, 0, 0, 0, 0);
251 #else
252     OUTPUT_FLOAT(f, 0, reqDecimals, 0, 0, 0, 0);
253 #endif
254     OUTPUT_CHAR ('e', p);
255     if (exp<0) {
256       OUTPUT_CHAR ('-', p);
257       exp = -exp;
258     }
259     OUTPUT_CHAR ('0'+exp/10, p);
260     OUTPUT_CHAR ('0'+exp%10, p);
261 #ifdef SDCC_STACK_AUTO
262     return charsOutputted;
263 #else
264     return;
265 #endif //SDCC_STACK_AUTO
266   }
267
268   // split the float
269   integerPart=f;
270   decimalPart=f-integerPart;
271
272   // fill the buffer with the integerPart (in reversed order!)
273   while (integerPart) {
274     fpBuffer[fpBI++]='0' + integerPart%10;
275     integerPart /= 10;
276   }
277   if (!fpBI) {
278     // we need at least a 0
279     fpBuffer[fpBI++]='0';
280   }
281
282   // display some decimals as default
283   if (reqDecimals==-1)
284     reqDecimals=DEFAULT_FLOAT_PRECISION;
285
286   // fill buffer with the decimalPart (in normal order)
287   fpBD=fpBI;
288
289   for (i=reqDecimals; i>1; i--) {
290       decimalPart *= 10.0;
291       // truncate the float
292       integerPart=decimalPart;
293       fpBuffer[fpBD++]='0' + integerPart;
294       decimalPart-=integerPart;
295   }
296   if (i) {
297     decimalPart *= 10.0;
298     // truncate the float
299     integerPart = decimalPart + 0.5;
300     fpBuffer[fpBD++] = '0' + integerPart;
301   }
302
303   minWidth=fpBI; // we need at least these
304   minWidth+=reqDecimals?reqDecimals+1:0; // maybe these
305   if (negative || sign || space)
306     minWidth++; // and maybe even this :)
307
308   if (!left && reqWidth>i) {
309     if (zero) {
310       if (negative)
311       {
312         OUTPUT_CHAR('-', p);
313       }
314       else if (sign)
315       {
316         OUTPUT_CHAR('+', p);
317       }
318       else if (space)
319       {
320         OUTPUT_CHAR(' ', p);
321       }
322       while (reqWidth-->minWidth)
323       {
324         OUTPUT_CHAR('0', p);
325       }
326     } else {
327       while (reqWidth-->minWidth)
328       {
329         OUTPUT_CHAR(' ', p);
330       }
331       if (negative)
332       {
333         OUTPUT_CHAR('-', p);
334       }
335       else if (sign)
336       {
337         OUTPUT_CHAR('+', p);
338       }
339       else if (space)
340       {
341         OUTPUT_CHAR(' ', p);
342       }
343     }
344   } else {
345     if (negative)
346     {
347       OUTPUT_CHAR('-', p);
348     }
349     else if (sign)
350     {
351       OUTPUT_CHAR('+', p);
352     }
353     else if (space)
354     {
355       OUTPUT_CHAR(' ', p);
356     }
357   }
358
359   // output the integer part
360   i=fpBI-1;
361   do {
362     OUTPUT_CHAR (fpBuffer[i], p);
363   } while (i--);
364
365   // ouput the decimal part
366   if (reqDecimals) {
367     OUTPUT_CHAR ('.', p);
368     i=fpBI;
369     while (reqDecimals--)
370     {
371       OUTPUT_CHAR (fpBuffer[i++], p);
372     }
373   }
374
375   if (left && reqWidth>minWidth) {
376     while (reqWidth-->minWidth)
377     {
378       OUTPUT_CHAR(' ', p);
379     }
380   }
381 #ifdef SDCC_STACK_AUTO
382   return charsOutputted;
383 #else
384   return;
385 #endif //SDCC_STACK_AUTO
386 }
387 #endif //USE_FLOATS
388
389 int _print_format (pfn_outputchar pfn, void* pvoid, const char *format, va_list ap)
390 {
391   BOOL   left_justify;
392   BOOL   zero_padding;
393   BOOL   prefix_sign;
394   BOOL   prefix_space;
395   BOOL   signed_argument;
396   BOOL   char_argument;
397   BOOL   long_argument;
398   BOOL   float_argument;
399 #ifdef SDCC_STACK_AUTO
400   BOOL   lower_case;
401   value_t value;
402   int charsOutputted;
403 #endif
404   BOOL   lsd;
405
406   unsigned char radix;
407   unsigned char  width;
408   signed char decimals;
409   unsigned char  length;
410   char           c;
411
412 #ifdef SDCC_STACK_AUTO
413   #define output_char   pfn
414   #define p             pvoid
415 #else
416   output_char = pfn;
417   p = pvoid;
418 #endif
419
420   // reset output chars
421   charsOutputted = 0;
422
423 #ifdef SDCC_ds390
424   if (format==0) {
425     format=NULL_STRING;
426   }
427 #endif
428
429   while( c=*format++ )
430   {
431     if ( c=='%' )
432     {
433       left_justify    = 0;
434       zero_padding    = 0;
435       prefix_sign     = 0;
436       prefix_space    = 0;
437       signed_argument = 0;
438       char_argument   = 0;
439       long_argument   = 0;
440       float_argument  = 0;
441       radix           = 0;
442       width           = 0;
443       decimals        = -1;
444
445 get_conversion_spec:
446
447       c = *format++;
448
449       if (c=='%') {
450         OUTPUT_CHAR(c, p);
451         continue;
452       }
453
454       if (isdigit(c)) {
455         if (decimals==-1) {
456           width = 10*width + (c - '0');
457           if (width == 0) {
458             /* first character of width is a zero */
459             zero_padding = 1;
460           }
461         } else {
462           decimals = 10*decimals + (c-'0');
463         }
464         goto get_conversion_spec;
465       }
466
467       if (c=='.') {
468         if (decimals=-1) decimals=0;
469         else
470           ; // duplicate, ignore
471         goto get_conversion_spec;
472       }
473
474       if (islower(c))
475       {
476         c = toupper(c);
477         lower_case = 1;
478       }
479       else
480         lower_case = 0;
481
482       switch( c )
483       {
484       case '-':
485         left_justify = 1;
486         goto get_conversion_spec;
487       case '+':
488         prefix_sign = 1;
489         goto get_conversion_spec;
490       case ' ':
491         prefix_space = 1;
492         goto get_conversion_spec;
493       case 'B':
494         char_argument = 1;
495         goto get_conversion_spec;
496       case 'L':
497         long_argument = 1;
498         goto get_conversion_spec;
499
500       case 'C':
501         if( char_argument )
502           c = va_arg(ap,char);
503         else
504           c = va_arg(ap,int);
505         OUTPUT_CHAR( c, p );
506         break;
507
508       case 'S':
509         PTR = va_arg(ap,ptr_t);
510
511 #ifdef SDCC_ds390
512         if (PTR==0) {
513           PTR=NULL_STRING;
514           length=NULL_STRING_LENGTH;
515         } else {
516           length = strlen(PTR);
517         }
518 #else
519         length = strlen(PTR);
520 #endif
521         if ( decimals == -1 )
522         {
523           decimals = length;
524         }
525         if ( ( !left_justify ) && (length < width) )
526         {
527           width -= length;
528           while( width-- != 0 )
529           {
530             OUTPUT_CHAR( ' ', p );
531           }
532         }
533
534         while ( (c = *PTR)  && (decimals-- > 0))
535         {
536           OUTPUT_CHAR( c, p );
537           PTR++;
538         }
539
540         if ( left_justify && (length < width))
541         {
542           width -= length;
543           while( width-- != 0 )
544           {
545             OUTPUT_CHAR( ' ', p );
546           }
547         }
548         break;
549
550       case 'P':
551         PTR = va_arg(ap,ptr_t);
552
553 #if defined (SDCC_ds390)
554         {
555           unsigned char memtype = value.byte[3];
556           if (memtype > 0x80)
557             c = 'C';
558           else if (memtype > 0x60)
559             c = 'P';
560           else if (memtype > 0x40)
561             c = 'I';
562           else
563             c = 'X';
564         }
565         OUTPUT_CHAR(c, p);
566         OUTPUT_CHAR(':', p);
567         OUTPUT_CHAR('0', p);
568         OUTPUT_CHAR('x', p);
569         OUTPUT_2DIGITS( value.byte[2] );
570         OUTPUT_2DIGITS( value.byte[1] );
571         OUTPUT_2DIGITS( value.byte[0] );
572 #elif defined (SDCC_mcs51)
573         {
574           unsigned char memtype = value.byte[2];
575           if (memtype > 0x80)
576             c = 'C';
577           else if (memtype > 0x60)
578             c = 'P';
579           else if (memtype > 0x40)
580             c = 'I';
581           else
582             c = 'X';
583         }
584         OUTPUT_CHAR(c, p);
585         OUTPUT_CHAR(':', p);
586         OUTPUT_CHAR('0', p);
587         OUTPUT_CHAR('x', p);
588         if ((c != 'I' /* idata */) &&
589             (c != 'P' /* pdata */))
590         {
591           OUTPUT_2DIGITS( value.byte[1] );
592         }
593         OUTPUT_2DIGITS( value.byte[0] );
594 #else
595         OUTPUT_CHAR('0', p);
596         OUTPUT_CHAR('x', p);
597         OUTPUT_2DIGITS( value.byte[1] );
598         OUTPUT_2DIGITS( value.byte[0] );
599 #endif
600         break;
601
602       case 'D':
603       case 'I':
604         signed_argument = 1;
605         radix = 10;
606         break;
607
608       case 'O':
609         radix = 8;
610         break;
611
612       case 'U':
613         radix = 10;
614         break;
615
616       case 'X':
617         radix = 16;
618         break;
619
620       case 'F':
621         float_argument=1;
622         break;
623
624       default:
625         // nothing special, just output the character
626         OUTPUT_CHAR( c, p );
627         break;
628       }
629
630       if (float_argument) {
631         value.f=va_arg(ap,float);
632 #if !USE_FLOATS
633         PTR="<NO FLOAT>";
634         while (c=*PTR++)
635         {
636           OUTPUT_CHAR (c, p);
637         }
638         // treat as long hex
639         //radix=16;
640         //long_argument=1;
641         //zero_padding=1;
642         //width=8;
643 #else
644         // ignore b and l conversion spec for now
645 #ifdef SDCC_STACK_AUTO
646         charsOutputted += OUTPUT_FLOAT(value.f, width, decimals, left_justify,
647                                      zero_padding, prefix_sign, prefix_space);
648 #else
649         OUTPUT_FLOAT(value.f, width, decimals, left_justify,
650                      zero_padding, prefix_sign, prefix_space);
651 #endif //SDCC_STACK_AUTO
652 #endif //USE_FLOATS
653       } else if (radix != 0)
654       {
655         // Apperently we have to output an integral type
656         // with radix "radix"
657         unsigned char store[6];
658         unsigned char _AUTOMEM *pstore = &store[5];
659
660         // store value in byte[0] (LSB) ... byte[3] (MSB)
661         if (char_argument)
662         {
663           value.l = va_arg(ap,char);
664           if (!signed_argument)
665           {
666             value.l &= 0xFF;
667           }
668         }
669         else if (long_argument)
670         {
671           value.l = va_arg(ap,long);
672         }
673         else // must be int
674         {
675           value.l = va_arg(ap,int);
676           if (!signed_argument)
677           {
678             value.l &= 0xFFFF;
679           }
680         }
681
682         if ( signed_argument )
683         {
684           if (value.l < 0)
685             value.l = -value.l;
686           else
687             signed_argument = 0;
688         }
689
690         length=0;
691         lsd = 1;
692
693         do {
694           value.byte[4] = 0;
695 #if defined SDCC_STACK_AUTO
696           calculate_digit(&value, radix);
697 #else
698           calculate_digit(radix);
699 #endif
700           if (!lsd)
701           {
702             *pstore = (value.byte[4] << 4) | (value.byte[4] >> 4) | *pstore;
703             pstore--;
704           }
705           else
706           {
707             *pstore = value.byte[4];
708           }
709           length++;
710           lsd = !lsd;
711         } while( value.ul );
712
713         if (width == 0)
714         {
715           // default width. We set it to 1 to output
716           // at least one character in case the value itself
717           // is zero (i.e. length==0)
718           width=1;
719         }
720
721         /* prepend spaces if needed */
722         if (!zero_padding && !left_justify)
723         {
724           while ( width > (unsigned char) (length+1) )
725           {
726             OUTPUT_CHAR( ' ', p );
727             width--;
728           }
729         }
730
731         if (signed_argument) // this now means the original value was negative
732         {
733           OUTPUT_CHAR( '-', p );
734           // adjust width to compensate for this character
735           width--;
736         }
737         else if (length != 0)
738         {
739           // value > 0
740           if (prefix_sign)
741           {
742             OUTPUT_CHAR( '+', p );
743             // adjust width to compensate for this character
744             width--;
745           }
746           else if (prefix_space)
747           {
748             OUTPUT_CHAR( ' ', p );
749             // adjust width to compensate for this character
750             width--;
751           }
752         }
753
754         /* prepend zeroes/spaces if needed */
755         if (!left_justify)
756           while ( width-- > length )
757           {
758             OUTPUT_CHAR( zero_padding ? '0' : ' ', p );
759           }
760         else
761         {
762           /* spaces are appended after the digits */
763           if (width > length)
764             width -= length;
765           else
766             width = 0;
767         }
768
769         /* output the digits */
770         while( length-- )
771         {
772           lsd = !lsd;
773           if (!lsd)
774           {
775             pstore++;
776             value.byte[4] = *pstore >> 4;
777           }
778           else
779           {
780             value.byte[4] = *pstore & 0x0F;
781           }
782 #ifdef SDCC_STACK_AUTO
783           output_digit( value.byte[4], lower_case, output_char, p );
784           charsOutputted++;
785 #else
786           output_digit( value.byte[4] );
787 #endif
788         }
789         if (left_justify)
790           while (width-- > 0)
791           {
792             OUTPUT_CHAR(' ', p);
793           }
794       }
795     }
796     else
797     {
798       // nothing special, just output the character
799       OUTPUT_CHAR( c, p );
800     }
801   }
802
803   return charsOutputted;
804 }
805
806 /****************************************************************************/