gbdk-releases/sdcc/device/lib/vprintf.c
2015-01-10 16:25:09 +01:00

626 lines
13 KiB
C

/*-------------------------------------------------------------------------
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 <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <stdio.h>
#define PTR value.p
#ifdef SDCC_ds390
#define NULL_STRING "<NULL>"
#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="<NO FLOAT>";
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 = <lsd>
mov a,_value+4 ; a = <msd>
swap a
orl b,a ; b = <msd><lsd>
push b
sjmp 2$
1$:
mov a,_value+4 ; a = <lsd>
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 = <msd><lsd>
nop ; to disable the "optimizer"
push acc
swap a
anl a,#0x0F ; a = <msd>
sjmp 4$
3$:
pop acc
anl a,#0x0F ; a = <lsd>
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 );
}