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