* device/lib/vprintf.c (vsprintf): minor optimization, see bug #801101
[fw/sdcc] / device / lib / vprintf.c
1 /*-------------------------------------------------------------------------
2   vprintf.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
7    This program is free software; you can redistribute it and/or modify it
8    under the terms of the GNU General Public License as published by the
9    Free Software Foundation; either version 2, or (at your option) any
10    later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with this program; if not, write to the Free Software
19    Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20
21    In other words, you are welcome to use, share and improve this program.
22    You are forbidden to forbid anyone else to use, share and improve
23    what you give them.   Help stamp out software-hoarding!
24 -------------------------------------------------------------------------*/
25
26 /* this module uses some global variables instead function parameters, so: */
27 #ifdef SDCC_STACK_AUTO
28 #warning "this module cannot yet be use as a reentrant one"
29 #endif
30
31 #if defined(__ds390)
32 #define USE_FLOATS 1
33 #endif
34
35 #include <stdarg.h>
36 #include <string.h>
37 #include <ctype.h>
38 #include <stdio.h>
39
40 #define PTR value.p
41
42 #ifdef SDCC_ds390
43 #define NULL_STRING "<NULL>"
44 #define NULL_STRING_LENGTH 6
45 #endif
46
47 /****************************************************************************/
48
49 //typedef char * ptr_t;
50 #define ptr_t char *
51
52 #ifdef toupper
53 #undef toupper
54 #endif
55
56 //#define toupper(c) ((c)&=~0x20)
57 #define toupper(c) ((c)&=0xDF)
58
59 typedef union
60 {
61   unsigned char  byte[5];
62   long           l;
63   unsigned long  ul;
64   float          f;
65   char           *p;
66 } value_t;
67
68
69 static code char memory_id[] = "IXCP-";
70
71 static ptr_t output_ptr;
72 static bit   output_to_string;
73 static bit   lower_case;
74 static bit   lsd;
75
76 /* this one NEEDS to be in data */
77 static data value_t value;
78
79 static unsigned char radix;
80
81 // this makes the whole dammed thing nonreentrent
82 static int charsOutputted;
83
84 /****************************************************************************/
85
86 static void output_char( char c ) reentrant
87 {
88   if (output_to_string)
89   {
90     *output_ptr++ = c;
91   }
92   else
93   {
94     putchar( c );
95   }
96   charsOutputted++;
97 }
98
99 /*--------------------------------------------------------------------------*/
100
101 static void output_digit( unsigned char n ) reentrant
102 {
103   output_char( n <= 9 ? '0'+n : 
104                (lower_case ? n+(char)('a'-10) : n+(char)('A'-10)) );
105 }
106
107 /*--------------------------------------------------------------------------*/
108
109 static void output_2digits( unsigned char b ) reentrant
110 {
111   output_digit( b>>4 );
112   output_digit( b&0x0F );
113 }
114         
115 /*--------------------------------------------------------------------------*/
116
117 static void calculate_digit( void )
118 {
119   unsigned char i;
120
121   for( i = 32; i != 0; i-- )
122   {
123 _asm
124   clr  c
125   mov  a,_value+0  
126   rlc  a
127   mov  _value+0,a
128   mov  a,_value+1
129   rlc  a
130   mov  _value+1,a
131   mov  a,_value+2
132   rlc  a
133   mov  _value+2,a
134   mov  a,_value+3
135   rlc  a
136   mov  _value+3,a
137   mov  a,_value+4
138   rlc  a
139   mov  _value+4,a
140 _endasm;
141
142     if (radix <= value.byte[4] )
143     {
144       value.byte[4] -= radix;
145       value.byte[0]++;
146     }
147   }
148 }
149
150 #if USE_FLOATS
151
152 /* This is a very inefficient but direct approach, since we have no math
153    library yet (e.g. log()).
154    It does most of the modifiers, but has some restrictions. E.g. the 
155    abs(float) shouldn't be bigger than an unsigned long (that's 
156    about 4294967295), but still makes it usefull for most real-life
157    applications.
158 */
159
160 #define DEFAULT_FLOAT_PRECISION 6
161
162 static void output_float (float f, unsigned char reqWidth, 
163                           signed char reqDecimals,
164                           bit left, bit zero, bit sign, bit space)
165 {
166   char negative=0;
167   unsigned long integerPart;
168   float decimalPart;
169   char fpBuffer[128];
170   char fpBI=0, fpBD;
171   unsigned char minWidth, i;
172
173   // save the sign
174   if (f<0) {
175     negative=1;
176     f=-f;
177   }
178
179   if (f>0x00ffffff) {
180     // this part is from Frank van der Hulst
181     signed char exp;
182     
183     for (exp = 0; f >= 10.0; exp++) f /=10.0;
184     for (       ; f < 1.0;   exp--) f *=10.0;
185
186     if (negative) {
187       putchar ('-');
188     } else {
189       if (sign) {
190         putchar ('+');
191       }
192     }
193     output_float(f, 0, reqDecimals, 0, 0, 0, 0);
194     putchar ('e');
195     if (exp<0) {
196       putchar ('-');
197       exp = -exp;
198     }
199     putchar ('0'+exp/10);
200     putchar ('0'+exp%10);
201     return;
202   }
203
204   // split the float
205   integerPart=f;
206   decimalPart=f-integerPart;
207
208   // fill the buffer with the integerPart (in reversed order!)
209   while (integerPart) {
210     fpBuffer[fpBI++]='0' + integerPart%10;
211     integerPart /= 10;
212   }
213   if (!fpBI) {
214     // we need at least a 0
215     fpBuffer[fpBI++]='0';
216   }
217
218   // display some decimals as default
219   if (reqDecimals==-1)
220     reqDecimals=DEFAULT_FLOAT_PRECISION;
221   
222   // fill buffer with the decimalPart (in normal order)
223   fpBD=fpBI;
224   if (i=reqDecimals /* that's an assignment */) {
225     do {
226       decimalPart *= 10.0;
227       // truncate the float
228       integerPart=decimalPart;
229       fpBuffer[fpBD++]='0' + integerPart;
230       decimalPart-=integerPart;
231     } while (--i);
232   }
233   
234   minWidth=fpBI; // we need at least these
235   minWidth+=reqDecimals?reqDecimals+1:0; // maybe these
236   if (negative || sign || space)
237     minWidth++; // and maybe even this :)
238   
239   if (!left && reqWidth>i) {
240     if (zero) {
241       if (negative) output_char('-');
242       else if (sign) output_char('+');
243       else if (space) output_char(' ');
244       while (reqWidth-->minWidth)
245         output_char ('0');
246     } else {
247       while (reqWidth-->minWidth)
248         output_char (' ');
249       if (negative) output_char('-');
250       else if (sign) output_char('+');
251       else if (space) output_char (' ');
252     }
253   } else {
254     if (negative) output_char('-');
255     else if (sign) output_char('+');
256     else if (space) output_char(' ');
257   }
258
259   // output the integer part
260   i=fpBI-1;
261   do {
262     output_char (fpBuffer[i]);
263   } while (i--);
264   
265   // ouput the decimal part
266   if (reqDecimals) {
267     output_char ('.');
268     i=fpBI;
269     while (reqDecimals--)
270       output_char (fpBuffer[i++]);
271   }
272
273   if (left && reqWidth>minWidth) {
274     while (reqWidth-->minWidth)
275       output_char(' ');
276   }
277 }
278 #endif
279
280 /*--------------------------------------------------------------------------*/
281
282 int vsprintf (const char *buf, const char *format, va_list ap)
283 {
284   static bit            left_justify;
285   static bit            zero_padding;
286   static bit            prefix_sign;
287   static bit            prefix_space;
288   static bit            signed_argument;
289   static bit            char_argument;
290   static bit            long_argument;
291   static bit            float_argument;
292
293   unsigned char  width;
294   signed char decimals;
295   unsigned char  length;
296   char           c;
297
298   // reset output chars
299   charsOutputted=0;
300
301   output_ptr = buf;
302   if ( !buf )
303   {
304     output_to_string = 0;
305   }
306   else
307   {
308     output_to_string = 1;
309   }
310
311 #ifdef SDCC_ds390
312   if (format==0) {
313     format=NULL_STRING;
314   }
315 #endif
316  
317   while( c=*format++ )
318   {
319     if ( c=='%' )
320     {
321       left_justify    = 0;
322       zero_padding    = 0;
323       prefix_sign     = 0;
324       prefix_space    = 0;
325       signed_argument = 0;
326       radix           = 0;
327       char_argument   = 0;
328       long_argument   = 0;
329       float_argument  = 0;
330       width           = 0;
331       decimals        = -1;
332
333 get_conversion_spec:
334
335       c = *format++;
336
337       if (c=='%') {
338         output_char(c);
339         continue;
340       }
341
342       if (isdigit(c)) {
343         if (decimals==-1) {
344           width = 10*width + (c - '0');
345           if (width == 0) {
346             /* first character of width is a zero */
347             zero_padding = 1;
348           }
349         } else {
350           decimals = 10*decimals + (c-'0');
351         }
352         goto get_conversion_spec;
353       }
354
355       if (c=='.') {
356         if (decimals=-1) decimals=0;
357         else 
358           ; // duplicate, ignore
359         goto get_conversion_spec;
360       }
361
362       lower_case = islower(c);
363       if (lower_case)
364       {
365         c = toupper(c);
366       }
367
368       switch( c )
369       {
370       case '-':
371         left_justify = 1;
372         goto get_conversion_spec;
373       case '+':
374         prefix_sign = 1;
375         goto get_conversion_spec;
376       case ' ':
377         prefix_space = 1;
378         goto get_conversion_spec;
379       case 'B':
380         char_argument = 1;
381         goto get_conversion_spec;
382       case 'L':
383         long_argument = 1;
384         goto get_conversion_spec;
385
386       case 'C':
387         output_char( va_arg(ap,int) );
388         break;
389
390       case 'S':
391         PTR = va_arg(ap,ptr_t);
392
393 #ifdef SDCC_ds390
394         if (PTR==0) {
395           PTR=NULL_STRING;
396           length=NULL_STRING_LENGTH;
397         } else {
398           length = strlen(PTR);
399         }
400 #else
401         length = strlen(PTR);
402 #endif
403         if ( ( !left_justify ) && (length < width) )
404         {
405           width -= length;
406           while( width-- != 0 )
407           {
408             output_char( ' ' );
409           }
410         }
411
412         while ( *PTR )
413           output_char( *PTR++ );
414
415         if ( left_justify && (length < width))
416         {
417           width -= length;
418           while( width-- != 0 )
419           {
420             output_char( ' ' );
421           }
422         }
423         break;
424
425       case 'P':
426         PTR = va_arg(ap,ptr_t);
427
428 #ifdef SDCC_ds390
429         output_char(memory_id[(value.byte[3] > 3) ? 4 : value.byte[3]] );
430         output_char(':');
431         output_char('0');
432         output_char('x');
433         output_2digits(value.byte[2]);
434         output_2digits(value.byte[1]);
435         output_2digits(value.byte[0]);
436 #else
437         output_char( memory_id[(value.byte[2] > 3) ? 4 : value.byte[2]] );
438         output_char(':');
439         output_char('0');
440         output_char('x');
441         if ((value.byte[2] != 0x00 /* DSEG */) &&
442             (value.byte[2] != 0x03 /* SSEG */))
443           output_2digits( value.byte[1] );
444         output_2digits( value.byte[0] );
445 #endif
446         break;
447
448       case 'D':
449       case 'I':
450         signed_argument = 1;
451         radix = 10;
452         break;
453
454       case 'O':
455         radix = 8;
456         break;
457
458       case 'U':
459         radix = 10;
460         break;
461
462       case 'X':
463         radix = 16;
464         break;
465
466       case 'F':
467         float_argument=1;
468         break;
469         
470       default:
471         // nothing special, just output the character
472         output_char( c );
473         break;
474       }
475
476       if (float_argument) {
477         value.f=va_arg(ap,float);
478 #if !USE_FLOATS
479         PTR="<NO FLOAT>";
480         while (c=*PTR++)
481           output_char (c);
482         // treat as long hex
483         //radix=16;
484         //long_argument=1;
485         //zero_padding=1;
486         //width=8;
487 #else
488         // ignore b and l conversion spec for now
489         output_float(value.f, width, decimals, left_justify, zero_padding, 
490                      prefix_sign, prefix_space);
491 #endif
492       } else if (radix != 0)
493       {
494         // Apperently we have to output an integral type
495         // with radix "radix"
496
497         // store value in byte[0] (LSB) ... byte[3] (MSB)
498         if (char_argument)
499         {
500           value.l = va_arg(ap,char);
501           if (!signed_argument)
502           {
503             value.byte[1] = 0x00;
504             value.byte[2] = 0x00;
505             value.byte[3] = 0x00;
506           }
507         }
508         else if (long_argument)
509         {
510           value.l = va_arg(ap,long);
511         }
512         else // must be int
513         {
514           value.l = va_arg(ap,int);
515           if (!signed_argument)
516           {
517             value.byte[2] = 0x00;
518             value.byte[3] = 0x00;
519           }
520         }
521
522         if ( signed_argument )
523         {
524           if (value.l < 0)
525             value.l = -value.l;
526           else
527             signed_argument = 0;
528         }
529
530         length=0;
531         lsd = 1;
532
533         do {
534           value.byte[4] = 0;
535           calculate_digit();
536
537 _asm
538   jb   _lsd,1$
539   pop  b                ; b = <lsd>
540   mov  a,_value+4       ; a = <msd>
541   swap a
542   orl  b,a              ; b = <msd><lsd>
543   push b
544   sjmp 2$
545 1$:
546   mov  a,_value+4       ; a = <lsd>
547   push acc
548 2$:
549 _endasm;
550
551           length++;
552           lsd = ~lsd;
553         } while( (value.byte[0] != 0) || (value.byte[1] != 0) ||
554                  (value.byte[2] != 0) || (value.byte[3] != 0) );
555
556         if (width == 0)
557         {
558           // default width. We set it to 1 to output
559           // at least one character is case the value itself
560           // is zero (i.e. length==0)
561           width=1;
562         }
563
564         /* prepend spaces if needed */
565         if (!zero_padding && !left_justify)
566         {
567           while ( width > (unsigned char) (length+1) )
568           {
569             output_char( ' ' );
570             width--;
571           }
572         }
573
574         if (signed_argument) // this now means the original value was negative
575         {
576           output_char( '-' );
577           // adjust width to compensate for this character
578           width--;
579         }
580         else if (length != 0)
581         {
582           // value > 0
583           if (prefix_sign)
584           {
585             output_char( '+' );
586             // adjust width to compensate for this character
587             width--;
588           }
589           else if (prefix_space)
590           {
591             output_char( ' ' );
592             // adjust width to compensate for this character
593             width--;
594           }
595         }
596
597         /* prepend zeroes/spaces if needed */
598         if (!left_justify)
599           while ( width-- > length )
600           {
601             output_char( zero_padding ? '0' : ' ' );
602           }
603         else
604         {
605           /* spaces are appended after the digits */
606           if (width > length)
607             width -= length;
608           else
609             width = 0;
610         }
611
612         /* output the digits */
613         while( length-- )
614         {
615           lsd = ~lsd;
616
617 _asm
618   jb   _lsd,3$
619   pop  acc              ; a = <msd><lsd>
620   nop                   ; to disable the "optimizer"
621   push acc
622   swap a
623   anl  a,#0x0F          ; a = <msd>
624   sjmp 4$
625 3$:
626   pop  acc
627   anl  a,#0x0F          ; a = <lsd>
628 4$:
629   mov  _value+4,a
630 _endasm;
631
632           output_digit( value.byte[4] );
633         }
634         if (left_justify)
635           while (width-- > 0)
636             output_char(' ');
637       }
638     }
639     else
640     {
641       // nothing special, just output the character
642       output_char( c );
643     }
644   }
645
646   // Copy \0 to the end of buf
647   // Modified by JB 17/12/99
648   if (output_to_string) {
649     output_char(0);
650     return charsOutputted-1;
651   } else {
652     return charsOutputted;
653   }
654 }
655
656 /*--------------------------------------------------------------------------*/
657
658 int vprintf (const char *format, va_list ap)
659 {
660   return vsprintf( 0, format, ap );
661 }