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