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