]> git.gag.com Git - fw/sdcc/blob - device/lib/vprintf.c
Initial %f support
[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 #define USE_FLOATS 1
26
27 #include <stdarg.h>
28 #include <string.h>
29 #include <ctype.h>
30 #include <stdio.h>
31
32 #define PTR value.p 
33
34 /****************************************************************************/
35
36 typedef char _generic *ptr_t;
37
38 #ifdef toupper
39 #undef toupper
40 #endif
41
42 //#define toupper(c) ((c)&=~0x20)
43 #define toupper(c) ((c)&=0xDF)
44
45 typedef union
46 {
47   unsigned char  byte[5];
48   long           l;
49   unsigned long  ul;
50   float          f;
51   char _generic *p;
52 } value_t;
53
54
55 static code char memory_id[] = "IXCP-";
56
57 ptr_t output_ptr;
58 bit   output_to_string;
59 bit   lower_case;
60 bit   lsd;
61
62 data value_t        value;
63
64 unsigned short      radix;
65
66 /****************************************************************************/
67
68 static void output_char( char c ) reentrant
69 {
70   if (output_to_string)
71   {
72     *output_ptr++ = c;
73   }
74   else
75   {
76     putchar( c );
77   }
78 }
79
80 /*--------------------------------------------------------------------------*/
81
82 static void output_digit( unsigned char n ) reentrant
83 {
84   output_char( n <= 9 ? '0'+n : (lower_case ? n+(char)('a'-10) : n+(char)('A'-10)) );
85 }
86
87 /*--------------------------------------------------------------------------*/
88
89 static void output_2digits( unsigned char b ) reentrant
90 {
91   output_digit( b>>4 );
92   output_digit( b&0x0F );
93 }
94         
95 /*--------------------------------------------------------------------------*/
96
97 static void calculate_digit( void )
98 {
99   unsigned char i;
100
101   for( i = 32; i != 0; i-- )
102   {
103 _asm
104   clr  c
105   mov  a,_value+0
106   rlc  a
107   mov  _value+0,a
108   mov  a,_value+1
109   rlc  a
110   mov  _value+1,a
111   mov  a,_value+2
112   rlc  a
113   mov  _value+2,a
114   mov  a,_value+3
115   rlc  a
116   mov  _value+3,a
117   mov  a,_value+4
118   rlc  a
119   mov  _value+4,a
120 _endasm;
121
122     if (radix <= value.byte[4] )
123     {
124       value.byte[4] -= radix;
125       value.byte[0]++;
126     }
127   }
128 }
129
130 #if USE_FLOATS
131
132 /* This is a very inefficient but direct approach, since we have no math
133    library yet (e.g. log()).
134    It does most of the modifiers, but has some restrictions. E.g. the 
135    abs(float) shouldn't be bigger than an unsigned long (that's 
136    about 4294967295), but still makes it usefull for most real-life
137    applications.
138 */
139
140 #define DEFAULT_FLOAT_PRECISION 6
141
142 static void output_float (float f, unsigned char reqWidth, 
143                           signed char reqDecimals,
144                           bit left, bit zero, bit sign, bit space)
145 {
146   char negative=0;
147   long integerPart;
148   float decimalPart;
149   char fpBuffer[128];
150   char fpBI=0, fpBD;
151   unsigned char minWidth, i;
152
153   // save the sign
154   if (f<0) {
155     negative=1;
156     f=-f;
157   }
158
159   // split the float
160   integerPart=f;
161   decimalPart=f-integerPart;
162
163   // fill the buffer with the integerPart (in reversed order!)
164   while (integerPart) {
165     fpBuffer[fpBI++]='0' + integerPart%10;
166     integerPart /= 10;
167   }
168   if (!fpBI) {
169     // we need at least a 0
170     fpBuffer[fpBI++]='0';
171   }
172
173   // display some decimals as default
174   if (reqDecimals==-1)
175     reqDecimals=DEFAULT_FLOAT_PRECISION;
176   
177   // fill buffer with the decimalPart (in normal order)
178   fpBD=fpBI;
179   if (i=reqDecimals /* that's an assignment */) {
180     do {
181       decimalPart *= 10.0;
182       // truncate the float
183       integerPart=decimalPart;
184       fpBuffer[fpBD++]='0' + integerPart;
185       decimalPart-=integerPart;
186     } while (--i);
187   }
188   
189   minWidth=fpBI; // we need at least these
190   minWidth+=reqDecimals?reqDecimals+1:0; // maybe these
191   if (negative || sign || space)
192     minWidth++; // and maybe even this :)
193   
194   if (!left && reqWidth>i) {
195     if (zero) {
196       if (negative) output_char('-');
197       else if (sign) output_char('+');
198       else if (space) output_char(' ');
199       while (reqWidth-->minWidth)
200         output_char ('0');
201     } else {
202       while (reqWidth-->minWidth)
203         output_char (' ');
204       if (negative) output_char('-');
205       else if (sign) output_char('+');
206       else if (space) output_char (' ');
207     }
208   } else {
209     if (negative) output_char('-');
210     else if (sign) output_char('+');
211     else if (space) output_char(' ');
212   }
213
214   // output the integer part
215   i=fpBI-1;
216   do {
217     output_char (fpBuffer[i]);
218   } while (i--);
219   
220   // ouput the decimal part
221   if (reqDecimals) {
222     output_char ('.');
223     i=fpBI;
224     while (reqDecimals--)
225       output_char (fpBuffer[i++]);
226   }
227
228   if (left && reqWidth>minWidth) {
229     while (reqWidth-->minWidth)
230       output_char(' ');
231   }
232 }
233 #endif
234
235 /*--------------------------------------------------------------------------*/
236
237 int vsprintf (const char *buf, const char *format, va_list ap)
238 {
239   bit            left_justify;
240   bit            zero_padding;
241   bit            prefix_sign;
242   bit            prefix_space;
243   bit            signed_argument;
244   bit            char_argument;
245   bit            long_argument;
246   bit            float_argument;
247
248   unsigned char  width;
249   signed char decimals;
250   unsigned char  length;
251   char           c;
252
253   output_ptr = buf;
254   if ( !buf )
255   {
256     output_to_string = 0;
257   }
258   else
259   {
260     output_to_string = 1;
261   }
262
263   while( c=*format++ )
264   {
265     if ( c=='%' )
266     {
267       left_justify    = 0;
268       zero_padding    = 0;
269       prefix_sign     = 0;
270       prefix_space    = 0;
271       signed_argument = 0;
272       radix           = 0;
273       char_argument   = 0;
274       long_argument   = 0;
275       float_argument  = 0;
276       width           = 0;
277       decimals        = -1;
278
279 get_conversion_spec:
280
281       c = *format++;
282
283       if (c=='%') {
284         output_char(c);
285         continue;
286       }
287
288       if (isdigit(c)) {
289         if (decimals==-1) {
290           width = 10*width + (c - '0');
291           if (width == 0) {
292             /* first character of width is a zero */
293             zero_padding = 1;
294           }
295         } else {
296           decimals = 10*decimals + (c-'0');
297         }
298         goto get_conversion_spec;
299       }
300
301       if (c=='.') {
302         if (decimals=-1) decimals=0;
303         else 
304           ; // duplicate, ignore
305         goto get_conversion_spec;
306       }
307
308       lower_case = islower(c);
309       if (lower_case)
310       {
311         c = toupper(c);
312       }
313
314       switch( c )
315       {
316       case '-':
317         left_justify = 1;
318         goto get_conversion_spec;
319       case '+':
320         prefix_sign = 1;
321         goto get_conversion_spec;
322       case ' ':
323         prefix_space = 1;
324         goto get_conversion_spec;
325       case 'B':
326         char_argument = 1;
327         goto get_conversion_spec;
328       case 'L':
329         long_argument = 1;
330         goto get_conversion_spec;
331
332       case 'C':
333         output_char( va_arg(ap,unsigned char) );
334         break;
335
336       case 'S':
337         PTR = va_arg(ap,ptr_t);
338
339         length = strlen(PTR);
340         if ( ( !left_justify ) && (length < width) )
341         {
342           width -= length;
343           while( width-- != 0 )
344           {
345             output_char( ' ' );
346           }
347         }
348
349         while ( *PTR )
350           output_char( *PTR++ );
351
352         if ( left_justify && (length < width))
353         {
354           width -= length;
355           while( width-- != 0 )
356           {
357             output_char( ' ' );
358           }
359         }
360         break;
361
362       case 'P':
363         PTR = va_arg(ap,ptr_t);
364
365 #ifdef SDCC_MODEL_FLAT24
366         output_char(memory_id[(value.byte[3] > 3) ? 4 : value.byte[3]] );
367         output_char(':');
368         output_char('0');
369         output_char('x');
370         output_2digits(value.byte[2]);
371         output_2digits(value.byte[1]);
372         output_2digits(value.byte[0]);
373 #else
374         output_char( memory_id[(value.byte[2] > 3) ? 4 : value.byte[2]] );
375         output_char(':');
376         output_char('0');
377         output_char('x');
378         if ((value.byte[2] != 0x00 /* DSEG */) && (value.byte[2] != 0x03 /* SSEG */))
379           output_2digits( value.byte[1] );
380         output_2digits( value.byte[0] );
381 #endif
382         break;
383
384       case 'D':
385       case 'I':
386         signed_argument = 1;
387         radix = 10;
388         break;
389
390       case 'O':
391         radix = 8;
392         break;
393
394       case 'U':
395         radix = 10;
396         break;
397
398       case 'X':
399         radix = 16;
400         break;
401
402       case 'F':
403         float_argument=1;
404         break;
405         
406       default:
407         // nothing special, just output the character
408         output_char( c );
409         break;
410       }
411
412       if (float_argument) {
413         value.f=va_arg(ap,float);
414 #if !USE_FLOATS
415         // treat as long hex
416         radix=16;
417         long_argument=1;
418         zero_padding=1;
419         width=8;
420 #else
421         // ignore b and l conversion spec for now
422         output_float(value.f, width, decimals, left_justify, zero_padding, 
423                      prefix_sign, prefix_space);
424 #endif
425       } else if (radix != 0)
426       {
427         // Apperently we have to output an integral type
428         // with radix "radix"
429
430         // store value in byte[0] (LSB) ... byte[3] (MSB)
431         if (char_argument)
432         {
433           value.l = va_arg(ap,char);
434           if (!signed_argument)
435           {
436             value.byte[1] = 0x00;
437             value.byte[2] = 0x00;
438             value.byte[3] = 0x00;
439           }
440         }
441         else if (long_argument)
442         {
443           value.l = va_arg(ap,long);
444         }
445         else // must be int
446         {
447           value.l = va_arg(ap,int);
448           if (!signed_argument)
449           {
450             value.byte[2] = 0x00;
451             value.byte[3] = 0x00;
452           }
453         }
454
455         if ( signed_argument )
456         {
457           if (value.l < 0)
458             value.l = -value.l;
459           else
460             signed_argument = 0;
461         }
462
463         length=0;
464         lsd = 1;
465
466         //jwk20000814: do this at least once, e.g.: printf ("%d", (int)0);
467         do {
468           value.byte[4] = 0;
469           calculate_digit();
470
471 _asm
472   jb   _lsd,1$
473   pop  b                ; b = <lsd>
474   mov  a,_value+4       ; a = <msd>
475   swap a
476   orl  b,a              ; b = <msd><lsd>
477   push b
478   sjmp 2$
479 1$:
480   mov  a,_value+4       ; a = <lsd>
481   push acc
482 2$:
483 _endasm;
484
485           length++;
486           lsd = ~lsd;
487         } while( (value.byte[0] != 0) || (value.byte[1] != 0) ||
488                  (value.byte[2] != 0) || (value.byte[3] != 0) );
489         
490         if (width == 0)
491         {
492           // default width. We set it to 1 to output
493           // at least one character is case the value itself
494           // is zero (i.e. length==0)
495           width=1;
496         }
497
498         /* prepend spaces if needed */
499         if (!zero_padding)
500         {
501           while ( width > length+1 )
502           {
503             output_char( ' ' );
504             width--;
505           }
506         }
507
508         if (signed_argument) // this now means the original value was negative
509         {
510           output_char( '-' );
511           // adjust width to compensate for this character
512           width--;
513         }
514         else if (length != 0)
515         {
516           // value > 0
517           if (prefix_sign)
518           {
519             output_char( '+' );
520             // adjust width to compensate for this character
521             width--;
522           }
523           else if (prefix_space)
524           {
525             output_char( ' ' );
526             // adjust width to compensate for this character
527             width--;
528           }
529         }
530
531         /* prepend zeroes/spaces if needed */
532         while ( width-- > length )
533         {
534           output_char( zero_padding ? '0' : ' ' );
535         }
536
537         /* output the digits */
538         while( length-- )
539         {
540           lsd = ~lsd;
541
542 _asm
543   jb   _lsd,3$
544   pop  acc              ; a = <msd><lsd>
545   nop                   ; to disable the "optimizer"
546   push acc
547   swap a
548   anl  a,#0x0F          ; a = <msd>
549   sjmp 4$
550 3$:
551   pop  acc
552   anl  a,#0x0F          ; a = <lsd>
553 4$:
554   mov  _value+4,a
555 _endasm;
556
557           output_digit( value.byte[4] );
558         }
559       }
560     }
561     else
562     {
563       // nothing special, just output the character
564       output_char( c );
565     }
566   }
567        
568   // Copy \0 to the end of buf
569   // Modified by JB 17/12/99
570   if (output_to_string) output_char(0);
571 }
572
573 /*--------------------------------------------------------------------------*/
574
575 int vprintf (const char *format, va_list ap)
576 {
577   return vsprintf( 0, format, ap );
578 }