/*------------------------------------------------------------------------- vprintf.c - formatted output conversion Written By - Martijn van Balen aed@iae.nl (1999) Added %f By - johan.knol@iduna.nl (2000) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. In other words, you are welcome to use, share and improve this program. You are forbidden to forbid anyone else to use, share and improve what you give them. Help stamp out software-hoarding! -------------------------------------------------------------------------*/ /* this module uses some global variables instead function parameters, so: */ #ifdef SDCC_STACK_AUTO #warning "this module cannot yet be use as a reentrant one" #endif #ifdef __ds390 #define USE_FLOATS 1 #endif #include #include #include #include #define PTR value.p #ifdef SDCC_ds390 #define NULL_STRING "" #define NULL_STRING_LENGTH 6 #endif /****************************************************************************/ //typedef char * ptr_t; #define ptr_t char * #ifdef toupper #undef toupper #endif //#define toupper(c) ((c)&=~0x20) #define toupper(c) ((c)&=0xDF) typedef union { unsigned char byte[5]; long l; unsigned long ul; float f; char *p; } value_t; static code char memory_id[] = "IXCP-"; static ptr_t output_ptr; static bit output_to_string; static bit lower_case; static bit lsd; /* this one NEEDS to be in data */ static data value_t value; static unsigned char radix; // jwk: TODO: this makes the whole dammed thing nonreentrent static int charsOutputted; /****************************************************************************/ static void output_char( char c ) reentrant { if (output_to_string) { *output_ptr++ = c; } else { putchar( c ); } charsOutputted++; } /*--------------------------------------------------------------------------*/ static void output_digit( unsigned char n ) reentrant { output_char( n <= 9 ? '0'+n : (lower_case ? n+(char)('a'-10) : n+(char)('A'-10)) ); } /*--------------------------------------------------------------------------*/ static void output_2digits( unsigned char b ) reentrant { output_digit( b>>4 ); output_digit( b&0x0F ); } /*--------------------------------------------------------------------------*/ static void calculate_digit( void ) { unsigned char i; for( i = 32; i != 0; i-- ) { _asm clr c mov a,_value+0 rlc a mov _value+0,a mov a,_value+1 rlc a mov _value+1,a mov a,_value+2 rlc a mov _value+2,a mov a,_value+3 rlc a mov _value+3,a mov a,_value+4 rlc a mov _value+4,a _endasm; if (radix <= value.byte[4] ) { value.byte[4] -= radix; value.byte[0]++; } } } #if USE_FLOATS /* This is a very inefficient but direct approach, since we have no math library yet (e.g. log()). It does most of the modifiers, but has some restrictions. E.g. the abs(float) shouldn't be bigger than an unsigned long (that's about 4294967295), but still makes it usefull for most real-life applications. */ #define DEFAULT_FLOAT_PRECISION 6 static void output_float (float f, unsigned char reqWidth, signed char reqDecimals, bit left, bit zero, bit sign, bit space) { char negative=0; long integerPart; float decimalPart; char fpBuffer[128]; char fpBI=0, fpBD; unsigned char minWidth, i; // save the sign if (f<0) { negative=1; f=-f; } // split the float integerPart=f; decimalPart=f-integerPart; // fill the buffer with the integerPart (in reversed order!) while (integerPart) { fpBuffer[fpBI++]='0' + integerPart%10; integerPart /= 10; } if (!fpBI) { // we need at least a 0 fpBuffer[fpBI++]='0'; } // display some decimals as default if (reqDecimals==-1) reqDecimals=DEFAULT_FLOAT_PRECISION; // fill buffer with the decimalPart (in normal order) fpBD=fpBI; if (i=reqDecimals /* that's an assignment */) { do { decimalPart *= 10.0; // truncate the float integerPart=decimalPart; fpBuffer[fpBD++]='0' + integerPart; decimalPart-=integerPart; } while (--i); } minWidth=fpBI; // we need at least these minWidth+=reqDecimals?reqDecimals+1:0; // maybe these if (negative || sign || space) minWidth++; // and maybe even this :) if (!left && reqWidth>i) { if (zero) { if (negative) output_char('-'); else if (sign) output_char('+'); else if (space) output_char(' '); while (reqWidth-->minWidth) output_char ('0'); } else { while (reqWidth-->minWidth) output_char (' '); if (negative) output_char('-'); else if (sign) output_char('+'); else if (space) output_char (' '); } } else { if (negative) output_char('-'); else if (sign) output_char('+'); else if (space) output_char(' '); } // output the integer part i=fpBI-1; do { output_char (fpBuffer[i]); } while (i--); // ouput the decimal part if (reqDecimals) { output_char ('.'); i=fpBI; while (reqDecimals--) output_char (fpBuffer[i++]); } if (left && reqWidth>minWidth) { while (reqWidth-->minWidth) output_char(' '); } } #endif /*--------------------------------------------------------------------------*/ int vsprintf (const char *buf, const char *format, va_list ap) { static bit left_justify; static bit zero_padding; static bit prefix_sign; static bit prefix_space; static bit signed_argument; static bit char_argument; static bit long_argument; static bit float_argument; unsigned char width; signed char decimals; unsigned char length; char c; // reset output chars charsOutputted=0; output_ptr = buf; if ( !buf ) { output_to_string = 0; } else { output_to_string = 1; } #ifdef SDCC_ds390 if (format==0) { format=NULL_STRING; } #endif while( c=*format++ ) { if ( c=='%' ) { left_justify = 0; zero_padding = 0; prefix_sign = 0; prefix_space = 0; signed_argument = 0; radix = 0; char_argument = 0; long_argument = 0; float_argument = 0; width = 0; decimals = -1; get_conversion_spec: c = *format++; if (c=='%') { output_char(c); continue; } if (isdigit(c)) { if (decimals==-1) { width = 10*width + (c - '0'); if (width == 0) { /* first character of width is a zero */ zero_padding = 1; } } else { decimals = 10*decimals + (c-'0'); } goto get_conversion_spec; } if (c=='.') { if (decimals=-1) decimals=0; else ; // duplicate, ignore goto get_conversion_spec; } lower_case = islower(c); if (lower_case) { c = toupper(c); } switch( c ) { case '-': left_justify = 1; goto get_conversion_spec; case '+': prefix_sign = 1; goto get_conversion_spec; case ' ': prefix_space = 1; goto get_conversion_spec; case 'B': char_argument = 1; goto get_conversion_spec; case 'L': long_argument = 1; goto get_conversion_spec; case 'C': output_char( va_arg(ap,int) ); break; case 'S': PTR = va_arg(ap,ptr_t); #ifdef SDCC_ds390 if (PTR==0) { PTR=NULL_STRING; length=NULL_STRING_LENGTH; } else { length = strlen(PTR); } #else length = strlen(PTR); #endif if ( ( !left_justify ) && (length < width) ) { width -= length; while( width-- != 0 ) { output_char( ' ' ); } } while ( *PTR ) output_char( *PTR++ ); if ( left_justify && (length < width)) { width -= length; while( width-- != 0 ) { output_char( ' ' ); } } break; case 'P': PTR = va_arg(ap,ptr_t); #ifdef SDCC_ds390 output_char(memory_id[(value.byte[3] > 3) ? 4 : value.byte[3]] ); output_char(':'); output_char('0'); output_char('x'); output_2digits(value.byte[2]); output_2digits(value.byte[1]); output_2digits(value.byte[0]); #else output_char( memory_id[(value.byte[2] > 3) ? 4 : value.byte[2]] ); output_char(':'); output_char('0'); output_char('x'); if ((value.byte[2] != 0x00 /* DSEG */) && (value.byte[2] != 0x03 /* SSEG */)) output_2digits( value.byte[1] ); output_2digits( value.byte[0] ); #endif break; case 'D': case 'I': signed_argument = 1; radix = 10; break; case 'O': radix = 8; break; case 'U': radix = 10; break; case 'X': radix = 16; break; case 'F': float_argument=1; break; default: // nothing special, just output the character output_char( c ); break; } if (float_argument) { value.f=va_arg(ap,float); #if !USE_FLOATS PTR=""; while (c=*PTR++) output_char (c); // treat as long hex //radix=16; //long_argument=1; //zero_padding=1; //width=8; #else // ignore b and l conversion spec for now output_float(value.f, width, decimals, left_justify, zero_padding, prefix_sign, prefix_space); #endif } else if (radix != 0) { // Apperently we have to output an integral type // with radix "radix" // store value in byte[0] (LSB) ... byte[3] (MSB) if (char_argument) { value.l = va_arg(ap,char); if (!signed_argument) { value.byte[1] = 0x00; value.byte[2] = 0x00; value.byte[3] = 0x00; } } else if (long_argument) { value.l = va_arg(ap,long); } else // must be int { value.l = va_arg(ap,int); if (!signed_argument) { value.byte[2] = 0x00; value.byte[3] = 0x00; } } if ( signed_argument ) { if (value.l < 0) value.l = -value.l; else signed_argument = 0; } length=0; lsd = 1; //jwk20000814: do this at least once, e.g.: printf ("%d", (int)0); do { value.byte[4] = 0; calculate_digit(); _asm jb _lsd,1$ pop b ; b = mov a,_value+4 ; a = swap a orl b,a ; b = push b sjmp 2$ 1$: mov a,_value+4 ; a = push acc 2$: _endasm; length++; lsd = ~lsd; } while( (value.byte[0] != 0) || (value.byte[1] != 0) || (value.byte[2] != 0) || (value.byte[3] != 0) ); if (width == 0) { // default width. We set it to 1 to output // at least one character is case the value itself // is zero (i.e. length==0) width=1; } /* prepend spaces if needed */ if (!zero_padding) { while ( width > length+1 ) { output_char( ' ' ); width--; } } if (signed_argument) // this now means the original value was negative { output_char( '-' ); // adjust width to compensate for this character width--; } else if (length != 0) { // value > 0 if (prefix_sign) { output_char( '+' ); // adjust width to compensate for this character width--; } else if (prefix_space) { output_char( ' ' ); // adjust width to compensate for this character width--; } } /* prepend zeroes/spaces if needed */ while ( width-- > length ) { output_char( zero_padding ? '0' : ' ' ); } /* output the digits */ while( length-- ) { lsd = ~lsd; _asm jb _lsd,3$ pop acc ; a = nop ; to disable the "optimizer" push acc swap a anl a,#0x0F ; a = sjmp 4$ 3$: pop acc anl a,#0x0F ; a = 4$: mov _value+4,a _endasm; output_digit( value.byte[4] ); } } } else { // nothing special, just output the character output_char( c ); } } // Copy \0 to the end of buf // Modified by JB 17/12/99 if (output_to_string) { output_char(0); return charsOutputted-1; } else { return charsOutputted; } } /*--------------------------------------------------------------------------*/ int vprintf (const char *format, va_list ap) { return vsprintf( 0, format, ap ); }