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