That wasn't a good idea
[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 /****************************************************************************/
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 // jwk: TODO: 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   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   // split the float
180   integerPart=f;
181   decimalPart=f-integerPart;
182
183   // fill the buffer with the integerPart (in reversed order!)
184   while (integerPart) {
185     fpBuffer[fpBI++]='0' + integerPart%10;
186     integerPart /= 10;
187   }
188   if (!fpBI) {
189     // we need at least a 0
190     fpBuffer[fpBI++]='0';
191   }
192
193   // display some decimals as default
194   if (reqDecimals==-1)
195     reqDecimals=DEFAULT_FLOAT_PRECISION;
196   
197   // fill buffer with the decimalPart (in normal order)
198   fpBD=fpBI;
199   if (i=reqDecimals /* that's an assignment */) {
200     do {
201       decimalPart *= 10.0;
202       // truncate the float
203       integerPart=decimalPart;
204       fpBuffer[fpBD++]='0' + integerPart;
205       decimalPart-=integerPart;
206     } while (--i);
207   }
208   
209   minWidth=fpBI; // we need at least these
210   minWidth+=reqDecimals?reqDecimals+1:0; // maybe these
211   if (negative || sign || space)
212     minWidth++; // and maybe even this :)
213   
214   if (!left && reqWidth>i) {
215     if (zero) {
216       if (negative) output_char('-');
217       else if (sign) output_char('+');
218       else if (space) output_char(' ');
219       while (reqWidth-->minWidth)
220         output_char ('0');
221     } else {
222       while (reqWidth-->minWidth)
223         output_char (' ');
224       if (negative) output_char('-');
225       else if (sign) output_char('+');
226       else if (space) output_char (' ');
227     }
228   } else {
229     if (negative) output_char('-');
230     else if (sign) output_char('+');
231     else if (space) output_char(' ');
232   }
233
234   // output the integer part
235   i=fpBI-1;
236   do {
237     output_char (fpBuffer[i]);
238   } while (i--);
239   
240   // ouput the decimal part
241   if (reqDecimals) {
242     output_char ('.');
243     i=fpBI;
244     while (reqDecimals--)
245       output_char (fpBuffer[i++]);
246   }
247
248   if (left && reqWidth>minWidth) {
249     while (reqWidth-->minWidth)
250       output_char(' ');
251   }
252 }
253 #endif
254
255 /*--------------------------------------------------------------------------*/
256
257 int vsprintf (const char *buf, const char *format, va_list ap)
258 {
259   static bit            left_justify;
260   static bit            zero_padding;
261   static bit            prefix_sign;
262   static bit            prefix_space;
263   static bit            signed_argument;
264   static bit            char_argument;
265   static bit            long_argument;
266   static bit            float_argument;
267
268   unsigned char  width;
269   signed char decimals;
270   unsigned char  length;
271   char           c;
272
273   // reset output chars
274   charsOutputted=0;
275
276   output_ptr = buf;
277   if ( !buf )
278   {
279     output_to_string = 0;
280   }
281   else
282   {
283     output_to_string = 1;
284   }
285
286 #ifdef SDCC_ds390
287   if (format==0) {
288     format=NULL_STRING;
289   }
290 #endif
291  
292   while( c=*format++ )
293   {
294     if ( c=='%' )
295     {
296       left_justify    = 0;
297       zero_padding    = 0;
298       prefix_sign     = 0;
299       prefix_space    = 0;
300       signed_argument = 0;
301       radix           = 0;
302       char_argument   = 0;
303       long_argument   = 0;
304       float_argument  = 0;
305       width           = 0;
306       decimals        = -1;
307
308 get_conversion_spec:
309
310       c = *format++;
311
312       if (c=='%') {
313         output_char(c);
314         continue;
315       }
316
317       if (isdigit(c)) {
318         if (decimals==-1) {
319           width = 10*width + (c - '0');
320           if (width == 0) {
321             /* first character of width is a zero */
322             zero_padding = 1;
323           }
324         } else {
325           decimals = 10*decimals + (c-'0');
326         }
327         goto get_conversion_spec;
328       }
329
330       if (c=='.') {
331         if (decimals=-1) decimals=0;
332         else 
333           ; // duplicate, ignore
334         goto get_conversion_spec;
335       }
336
337       lower_case = islower(c);
338       if (lower_case)
339       {
340         c = toupper(c);
341       }
342
343       switch( c )
344       {
345       case '-':
346         left_justify = 1;
347         goto get_conversion_spec;
348       case '+':
349         prefix_sign = 1;
350         goto get_conversion_spec;
351       case ' ':
352         prefix_space = 1;
353         goto get_conversion_spec;
354       case 'B':
355         char_argument = 1;
356         goto get_conversion_spec;
357       case 'L':
358         long_argument = 1;
359         goto get_conversion_spec;
360
361       case 'C':
362         output_char( va_arg(ap,int) );
363         break;
364
365       case 'S':
366         PTR = va_arg(ap,ptr_t);
367
368 #ifdef SDCC_ds390
369         if (PTR==0) {
370           PTR=NULL_STRING;
371           length=NULL_STRING_LENGTH;
372         } else {
373           length = strlen(PTR);
374         }
375 #else
376         length = strlen(PTR);
377 #endif
378         if ( ( !left_justify ) && (length < width) )
379         {
380           width -= length;
381           while( width-- != 0 )
382           {
383             output_char( ' ' );
384           }
385         }
386
387         while ( *PTR )
388           output_char( *PTR++ );
389
390         if ( left_justify && (length < width))
391         {
392           width -= length;
393           while( width-- != 0 )
394           {
395             output_char( ' ' );
396           }
397         }
398         break;
399
400       case 'P':
401         PTR = va_arg(ap,ptr_t);
402
403 #ifdef SDCC_ds390
404         output_char(memory_id[(value.byte[3] > 3) ? 4 : value.byte[3]] );
405         output_char(':');
406         output_char('0');
407         output_char('x');
408         output_2digits(value.byte[2]);
409         output_2digits(value.byte[1]);
410         output_2digits(value.byte[0]);
411 #else
412         output_char( memory_id[(value.byte[2] > 3) ? 4 : value.byte[2]] );
413         output_char(':');
414         output_char('0');
415         output_char('x');
416         if ((value.byte[2] != 0x00 /* DSEG */) && 
417             (value.byte[2] != 0x03 /* SSEG */))
418           output_2digits( value.byte[1] );
419         output_2digits( value.byte[0] );
420 #endif
421         break;
422
423       case 'D':
424       case 'I':
425         signed_argument = 1;
426         radix = 10;
427         break;
428
429       case 'O':
430         radix = 8;
431         break;
432
433       case 'U':
434         radix = 10;
435         break;
436
437       case 'X':
438         radix = 16;
439         break;
440
441       case 'F':
442         float_argument=1;
443         break;
444         
445       default:
446         // nothing special, just output the character
447         output_char( c );
448         break;
449       }
450
451       if (float_argument) {
452         value.f=va_arg(ap,float);
453 #if !USE_FLOATS
454         PTR="<NO FLOAT>";
455         while (c=*PTR++)
456           output_char (c);
457         // treat as long hex
458         //radix=16;
459         //long_argument=1;
460         //zero_padding=1;
461         //width=8;
462 #else
463         // ignore b and l conversion spec for now
464         output_float(value.f, width, decimals, left_justify, zero_padding, 
465                      prefix_sign, prefix_space);
466 #endif
467       } else if (radix != 0)
468       {
469         // Apperently we have to output an integral type
470         // with radix "radix"
471
472         // store value in byte[0] (LSB) ... byte[3] (MSB)
473         if (char_argument)
474         {
475           value.l = va_arg(ap,char);
476           if (!signed_argument)
477           {
478             value.byte[1] = 0x00;
479             value.byte[2] = 0x00;
480             value.byte[3] = 0x00;
481           }
482         }
483         else if (long_argument)
484         {
485           value.l = va_arg(ap,long);
486         }
487         else // must be int
488         {
489           value.l = va_arg(ap,int);
490           if (!signed_argument)
491           {
492             value.byte[2] = 0x00;
493             value.byte[3] = 0x00;
494           }
495         }
496
497         if ( signed_argument )
498         {
499           if (value.l < 0)
500             value.l = -value.l;
501           else
502             signed_argument = 0;
503         }
504
505         length=0;
506         lsd = 1;
507
508         //jwk20000814: do this at least once, e.g.: printf ("%d", (int)0);
509         do {
510           value.byte[4] = 0;
511           calculate_digit();
512
513 _asm
514   jb   _lsd,1$
515   pop  b                ; b = <lsd>
516   mov  a,_value+4       ; a = <msd>
517   swap a
518   orl  b,a              ; b = <msd><lsd>
519   push b
520   sjmp 2$
521 1$:
522   mov  a,_value+4       ; a = <lsd>
523   push acc
524 2$:
525 _endasm;
526
527           length++;
528           lsd = ~lsd;
529         } while( (value.byte[0] != 0) || (value.byte[1] != 0) ||
530                  (value.byte[2] != 0) || (value.byte[3] != 0) );
531         
532         if (width == 0)
533         {
534           // default width. We set it to 1 to output
535           // at least one character is case the value itself
536           // is zero (i.e. length==0)
537           width=1;
538         }
539
540         /* prepend spaces if needed */
541         if (!zero_padding)
542         {
543           while ( width > length+1 )
544           {
545             output_char( ' ' );
546             width--;
547           }
548         }
549
550         if (signed_argument) // this now means the original value was negative
551         {
552           output_char( '-' );
553           // adjust width to compensate for this character
554           width--;
555         }
556         else if (length != 0)
557         {
558           // value > 0
559           if (prefix_sign)
560           {
561             output_char( '+' );
562             // adjust width to compensate for this character
563             width--;
564           }
565           else if (prefix_space)
566           {
567             output_char( ' ' );
568             // adjust width to compensate for this character
569             width--;
570           }
571         }
572
573         /* prepend zeroes/spaces if needed */
574         while ( width-- > length )
575         {
576           output_char( zero_padding ? '0' : ' ' );
577         }
578
579         /* output the digits */
580         while( length-- )
581         {
582           lsd = ~lsd;
583
584 _asm
585   jb   _lsd,3$
586   pop  acc              ; a = <msd><lsd>
587   nop                   ; to disable the "optimizer"
588   push acc
589   swap a
590   anl  a,#0x0F          ; a = <msd>
591   sjmp 4$
592 3$:
593   pop  acc
594   anl  a,#0x0F          ; a = <lsd>
595 4$:
596   mov  _value+4,a
597 _endasm;
598
599           output_digit( value.byte[4] );
600         }
601       }
602     }
603     else
604     {
605       // nothing special, just output the character
606       output_char( c );
607     }
608   }
609        
610   // Copy \0 to the end of buf
611   // Modified by JB 17/12/99
612   if (output_to_string) {
613     output_char(0);
614     return charsOutputted-1;
615   } else {
616     return charsOutputted;
617   }
618 }
619
620 /*--------------------------------------------------------------------------*/
621
622 int vprintf (const char *format, va_list ap)
623 {
624   return vsprintf( 0, format, ap );
625 }