psxsdk/libpsx/src/libc/printf.c

900 lines
17 KiB
C

/*
* printf.c
*
* Part of the PSXSDK C library
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SPRINTF_ALT_FLAG (1<<0)
#define SPRINTF_ZERO_FLAG (1<<1)
#define SPRINTF_NEGFIELD_FLAG (1<<2)
#define SPRINTF_SPACE_FLAG (1<<3)
#define SPRINTF_SIGN_FLAG (1<<4)
// sprintf() macros to calculate the real padding and to write it
// these were made to not repeat the code in the function
// they can only be used in sprintf()
// sprintf macros START
#define calculate_real_padding() \
y = 1; \
\
for(x=0;x<=19;x++) \
{ \
if(x == 0) \
pad_quantity--; \
else \
{ \
if(arg / y) \
pad_quantity--; \
} \
\
y *= 10; \
} \
\
if(pad_quantity < 0) pad_quantity = 0;
/*#define calculate_real_padding_hex() \
for (x = 0; x < 8; x++) \
{ \
if(x == 0) \
pad_quantity--; \
else \
{ \
if((arg >> (x * 4)) & 0xf) \
pad_quantity--; \
} \
}*/
#define calculate_real_padding_hex() \
last = 0; \
for (x = 0; x < 16; x++) \
if((arg >> (x * 4)) & 0xf) \
last = x; \
\
pad_quantity = (pad_quantity - 1) - last; \
if(pad_quantity < 0) pad_quantity = 0;
#define write_padding() \
if(!(flags & SPRINTF_NEGFIELD_FLAG)) \
for(x = 0; x < pad_quantity; x++) \
{ \
if(flags & SPRINTF_ZERO_FLAG) \
put_in_string(string, ssz, '0', string_pos++); \
else \
put_in_string(string, ssz, ' ', string_pos++); \
}
#define write_neg_padding() \
if(flags & SPRINTF_NEGFIELD_FLAG) \
{ \
for(x = 0; x < pad_quantity; x++) \
put_in_string(string, ssz, ' ', string_pos++);\
}
// sprintf macros END
enum
{
SPRINTF_SIZE_CHAR,
SPRINTF_SIZE_SHORT,
SPRINTF_SIZE_INT,
SPRINTF_SIZE_LONG,
SPRINTF_SIZE_LONG_LONG,
};
static unsigned int get_arg_in_size(int size, unsigned long long *arg, unsigned int check_sign)
{
int s = 0;
switch(size)
{
case SPRINTF_SIZE_CHAR:
*arg &= 0xff;
if(check_sign)
{
if(*arg & (1<<7))
{
*arg |= 0xffffff00;
*arg = ~(*arg - 1);
s = 1;
}
}
break;
case SPRINTF_SIZE_SHORT:
*arg &= 0xffff;
if(check_sign)
{
if(*arg & (1<<15))
{
*arg |= 0xffff0000;
*arg = ~(*arg - 1);
s = 1;
}
}
break;
// sizeof(long) == sizeof(int) on 32bit, so this will suffice for the psx
case SPRINTF_SIZE_INT:
case SPRINTF_SIZE_LONG:
*arg &= 0xffffffff;
if(check_sign)
{
if(*arg & (1<<31))
{
*arg |= (long long)0xffffffff00000000;
*arg = ~(*arg - 1);
s = 1;
}
}
break;
case SPRINTF_SIZE_LONG_LONG:
if(check_sign)
{
if(*arg & ((long long)1<<63))
{
*arg = ~(*arg - 1);
s = 1;
}
}
break;
}
return s;
}
static int libc_ulltoa(unsigned long long i, char *dst, int n, int nopad)
{
int x, y;
unsigned long long a, b;
int empty_digit = 1;
int sp=0;
int n2=0;
if(n<=0)
return 0;
for(x=18;x>=0;x--)
{
a = 1;
for(y = 0; y<x; y++)
a *= 10;
b = (i/a);
if(b>=1)
empty_digit = 0;
if(empty_digit == 0 || x == 0 || nopad == 1)
{
i -= b*a;
//put_in_string(string, ssz, b + '0', string_pos++);
if(n2!=(n-1))
{
//printf("n2=%d\n",n2);
dst[sp++] = b + '0';
n2++;
}
}
}
dst[sp] = 0;
return n2;
}
/*static void libc_float_to_string(float fl, char *dst, int n)
{
unsigned int *p = (unsigned int*)&fl;
unsigned long long i = 0;
unsigned long long f = 0;
int e, m, s;
int x, y;
unsigned long long z;
s = *p >> 31;
e = (*p >> 23) & 0xff;
m = *p & 0x7fffff;
if(e == 255 && m == 0) // Infinity
{
if(s) strncpy(dst, "-inf", n);
else strncpy(dst, "inf", n);
}else if(e == 255 && m != 0) // NaN
{
strncpy(dst, "nan", n);
}
else
{
e -= 127;
m |= 1<<23;
for(x = 23; x >= 0; x--)
{
if(m & (1<<x))
{
if(e >= 0)
{
z = 1;
for(y=0;y<e;y++)
z*=2;
i+=z;
}
else
{
z = 5000000000000000000;
for(y = 1; y < -e; y++)
z /= 2;
f+=z;
}
}
e--;
}
if(s && (n>0))
{
*(dst++) = '-';
n--;
}
x = libc_ulltoa(i, dst, n, 0);
n-=x+1;
dst+=x;
if(n>0)
{
*(dst++) = '.';
n--;
if(n>0)
{
x = libc_ulltoa(f, dst, n<6?n:6, 1);
n-=x;
dst+=x;
if(n>0)
*dst=0;
}
}
}
}*/
static void libc_double_to_string(double fl, char *dst, int n, int prec)
{
unsigned long long *p = (unsigned long long *)&fl;
unsigned long long i = 0;
unsigned long long f = 0;
unsigned long long m, s;
long long e;
int x;
unsigned long long z;
s = *p >> 63;
e = (*p >> 52) & 0x7ff;
//printf("%d\n", e);
m = *p & 0xfffffffffffff;
for(x=0;x<52;x++)
if(m&((unsigned long long)1<<(52-x))) putchar('1'); else putchar('0');
if(e == 255 && m == 0) // Infinity
{
if(s) strncpy(dst, "-inf", n);
else strncpy(dst, "inf", n);
}else if(e == 255 && m != 0) // NaN
{
strncpy(dst, "nan", n);
}
else
{
e -= 1023;
m |= (unsigned long long)1<<52;
for(x = 52; x >= 0; x--)
{
if(m & ((unsigned long long)1<<x))
{
if(e >= 0)
{
z = (long long)1<<e;
i+=z;
}
else
{
z = 5000000000000000000;
z >>= -(e + 1);
f+=z;
}
}
e--;
}
if(s && (n>0))
{
*(dst++) = '-';
n--;
}
x = libc_ulltoa(i, dst, n, 0);
n-=x+1;
dst+=x;
dprintf("N = %d\n", n);
if(n>0)
{
*(dst++) = '.';
if(n>0)
libc_ulltoa(f, dst, (n<(prec+1))?n:(prec+1), 1);
}
}
}
static char libc_sprintf_floatbuf[64];
static int __vsnprintf_internal(char *string, size_t size, const char *fmt, va_list ap, int (put_in_string(char *string, unsigned int sz, char c, int pos)))
{
int string_pos,fmt_pos;
int l;
unsigned long long arg;
char *argcp;
char *argcp_tmp;
int directive_coming = 0;
int flags = 0;
int argsize = 2; // int
int x, y;
unsigned long long a, b;
int empty_digit;
int ssz = size - 1;
int zero_flag_imp = 0;
int pad_quantity = 0;
int pad_quantity_f = -1;
int last;
if(size == 0)
ssz = 0;
l = strlen(fmt);
string_pos = 0;
for(fmt_pos=0;fmt_pos<l;fmt_pos++)
{
if(directive_coming)
{
switch(fmt[fmt_pos])
{
case '%':
put_in_string(string, ssz, '%', string_pos++);
directive_coming = 0;
break;
case ' ':
flags |= SPRINTF_SPACE_FLAG;
break;
case '#': // Specify alternate form
flags |= SPRINTF_ALT_FLAG;
break;
case '+': // Specify sign in signed conversions
flags |= SPRINTF_SIGN_FLAG;
break;
case '0': // Padding with zeros...
if(zero_flag_imp == 0)
{
flags |= SPRINTF_ZERO_FLAG;
zero_flag_imp = 1;
//printf("Zero padding enabled!\n");
}
else
{
pad_quantity *= 10;
//printf("pad_quantity = %d\n", pad_quantity);
}
break;
case '1' ... '9': // '...' cases are a GNU extension,
// but they simplify a lot
pad_quantity *= 10;
pad_quantity += fmt[fmt_pos] - '0';
zero_flag_imp = 1;
//printf("pad_quantity = %d\n", pad_quantity);
break;
case '-': // Negative field flag
if(flags & SPRINTF_ZERO_FLAG)
flags &= ~SPRINTF_ZERO_FLAG;
flags |= SPRINTF_NEGFIELD_FLAG;
break;
case '.': // Floating point precision
pad_quantity_f = pad_quantity;
pad_quantity = 0;
break;
case 'h': // Half argument size
if(argsize) argsize--;
break;
case 'l': // Double argument size
if(argsize < 2) argsize = 2;
else if(argsize < SPRINTF_SIZE_LONG_LONG) argsize++;
break;
// 'j', 't', 'z', 'q' added 2013-10-26 by nextvolume
case 'j': // Maximum integer size
argsize = SPRINTF_SIZE_LONG_LONG;
break;
case 't': // Size of ptrdiff_t (i.e. long on 32-bit, long long on 64-bit)
argsize = (sizeof(void*)==8)?
SPRINTF_SIZE_LONG_LONG:SPRINTF_SIZE_LONG;
break;
case 'z': // Size of size_t (int)
argsize = SPRINTF_SIZE_INT;
break;
case 'q': // Size of quad_t
argsize = SPRINTF_SIZE_LONG_LONG;
break;
case 'd': // signed decimal
case 'i':
empty_digit = 1;
//printf("argsize = %d\n", argsize);
if(argsize < SPRINTF_SIZE_LONG_LONG)
arg = (unsigned long long)va_arg(ap, unsigned int);
else
arg = va_arg(ap, unsigned long long);
if(flags & SPRINTF_SPACE_FLAG)
put_in_string(string, ssz, ' ', string_pos++);
if(get_arg_in_size(argsize, &arg, 1))
{
put_in_string(string, ssz, '-', string_pos++);
pad_quantity--;
}
else
{
if(flags & SPRINTF_SIGN_FLAG)
{
put_in_string(string, ssz, '+', string_pos++);
pad_quantity--;
}
}
/* Calculate how much padding we have to write */
/*y = 1;
for(x=0;x<=9;x++)
{
if(x == 0)
pad_quantity--;
else
{
if(arg / y)
pad_quantity--;
}
y *= 10;
}
if(pad_quantity < 0) pad_quantity = 0;*/
calculate_real_padding();
//printf("Actual pad quantity = %d\n", pad_quantity);
/*if(!(flags & SPRINTF_NEGFIELD_FLAG))
{
for(x = 0; x < pad_quantity; x++)
{
if(flags & SPRINTF_ZERO_FLAG)
put_in_string(string, ssz, '0', string_pos++);
else
put_in_string(string, ssz, ' ', string_pos++);
}
}*/
write_padding();
for(x=19;x>=0;x--)
{
a = 1;
for(y = 0; y<x; y++)
a *= 10;
b = (arg/a);
if(b>=1)
empty_digit = 0;
if(empty_digit == 0 || x == 0)
{
arg -= b*a;
put_in_string(string, ssz, b + '0', string_pos++);
}
}
/*if(flags & SPRINTF_NEGFIELD_FLAG)
{
for(x = 0; x < pad_quantity; x++)
put_in_string(string, ssz, ' ', string_pos++);
}*/
write_neg_padding();
directive_coming = 0;
break;
case 'u': // unsigned decimal
empty_digit = 1;
if(argsize < SPRINTF_SIZE_LONG_LONG)
arg = (unsigned long long)va_arg(ap, unsigned int);
else
arg = va_arg(ap, unsigned long long);
get_arg_in_size(argsize, &arg, 0);
calculate_real_padding();
write_padding();
for(x=19;x>=0;x--)
{
a = 1;
for(y = 0; y<x; y++)
a *= 10;
b = (arg/a);
if(b>=1)
empty_digit = 0;
if(empty_digit == 0 || x == 0)
{
arg -= b*a;
put_in_string(string, ssz, b + '0', string_pos++);
}
}
write_neg_padding();
directive_coming = 0;
break;
case 'x': // Hexadecimal
case 'X': // Hexadecimal with big letters
case 'p': // Hexadecimal with small letters with '0x' prefix
empty_digit = 1;
if(argsize < SPRINTF_SIZE_LONG_LONG)
arg = (unsigned long long)va_arg(ap, unsigned int);
else
arg = va_arg(ap, unsigned long long int);
get_arg_in_size(argsize, &arg, 0);
if(fmt_pos == 'p')
flags |= SPRINTF_ALT_FLAG;
if(flags & SPRINTF_ALT_FLAG)
{
put_in_string(string, ssz, '0', string_pos++);
if(fmt[fmt_pos] == 'X')
put_in_string(string, ssz, 'X', string_pos++);
else
put_in_string(string, ssz, 'x', string_pos++);
}
calculate_real_padding_hex();
write_padding();
for(x=15;x>=0;x--)
{
y = arg >> (x << 2);
y &= 0xf;
if(y>=1)
empty_digit = 0;
if(empty_digit == 0 || x == 0)
{
if(y>=0 && y<=9)
put_in_string(string, ssz, y + '0', string_pos++);
else if(y>=0xA && y<=0xF)
{
if(fmt[fmt_pos] == 'X')
put_in_string(string, ssz, (y - 0xa) + 'A', string_pos++);
else
put_in_string(string, ssz, (y - 0xa) + 'a', string_pos++);
}
}
}
write_neg_padding();
directive_coming = 0;
break;
case 'c': // character
arg = va_arg(ap, int);
put_in_string(string, ssz, arg & 0xff, string_pos++);
directive_coming = 0;
break;
case 's': // string
argcp = va_arg(ap, char *);
argcp_tmp = argcp;
if(argcp == NULL)
{
// Non standard extension, but supported by Linux and the BSDs.
put_in_string(string, ssz, '(', string_pos++);
put_in_string(string, ssz, 'n', string_pos++);
put_in_string(string, ssz, 'u', string_pos++);
put_in_string(string, ssz, 'l', string_pos++);
put_in_string(string, ssz, 'l', string_pos++);
put_in_string(string, ssz, ')', string_pos++);
directive_coming = 0;
break;
}
while(*argcp_tmp)
{
if(pad_quantity > 0) pad_quantity--;
argcp_tmp++;
}
if(!(flags & SPRINTF_NEGFIELD_FLAG))
{
while(pad_quantity > 0)
{
put_in_string(string,ssz, ' ', string_pos++);
pad_quantity--;
}
}
while(*argcp)
{
put_in_string(string, ssz, *argcp, string_pos++);
argcp++;
}
if(flags & SPRINTF_NEGFIELD_FLAG)
{
while(pad_quantity > 0)
{
put_in_string(string,ssz, ' ', string_pos++);
pad_quantity--;
}
}
directive_coming = 0;
break;
case 'o': // Octal
empty_digit = 1;
if(argsize < SPRINTF_SIZE_LONG_LONG)
arg = (unsigned long long)va_arg(ap, unsigned int);
else
arg = va_arg(ap, unsigned long long);
for(x=21;x>=0;x--)
{
y = arg >> (x * 3);
y &= 0x7;
if(y>=1)
empty_digit = 0;
if(empty_digit == 0 || x == 0)
put_in_string(string, ssz, y + '0', string_pos++);
}
directive_coming = 0;
break;
case '@': // Binary
empty_digit = 1;
if(argsize < SPRINTF_SIZE_LONG_LONG)
arg = (unsigned long long)va_arg(ap, unsigned int);
else
arg = va_arg(ap, unsigned long long);
for(x=63;x>=0;x--)
{
y = (arg >> x);
y &= 1;
if(y>=1)
empty_digit = 0;
if(empty_digit == 0 || x == 0)
put_in_string(string, ssz, y + '0', string_pos++);
}
directive_coming = 0;
break;
case 'f':
if(pad_quantity_f == -1)
pad_quantity_f = 6;
else
{
x = pad_quantity_f;
pad_quantity_f = pad_quantity;
pad_quantity = x;
}
dprintf("PRECISION = %d\n", pad_quantity_f);
libc_double_to_string(va_arg(ap, double), libc_sprintf_floatbuf, 64, pad_quantity_f);
// calculate padding
pad_quantity -= strlen(libc_sprintf_floatbuf);
write_padding();
for(x=0;libc_sprintf_floatbuf[x]!=0;x++)
put_in_string(string, ssz, libc_sprintf_floatbuf[x], string_pos++);
write_neg_padding();
directive_coming = 0;
break;
case 'n': // Number of characters written
*(va_arg(ap,unsigned int*)) = string_pos;
directive_coming = 0;
break;
default:
put_in_string(string, ssz, fmt[fmt_pos], string_pos++);
directive_coming = 0;
}
}
else
{
if(fmt[fmt_pos] == '%')
{
directive_coming = 1;
flags = 0;
argsize = 2;
pad_quantity = 0;
pad_quantity_f = -1;
zero_flag_imp = 0;
}
else
{
put_in_string(string, ssz, fmt[fmt_pos], string_pos++);
}
}
}
/*if(((size-1) < string_pos) && (size>0))
string[size - 1] = 0;
else
string[string_pos] = 0;*/
put_in_string(string, ssz, '\0', string_pos);
return string_pos;
}
static int vsnprintf_put_in_string(char *string, unsigned int sz, char c, int pos)
{
if(pos>=sz)
return 0;
else
string[pos] = c;
return 1;
}
int vsnprintf(char *string, size_t size, const char *fmt, va_list ap)
{
return __vsnprintf_internal(string, size, fmt, ap, vsnprintf_put_in_string);
}
static int sio_put_in_string(char *string, unsigned int sz, char c, int pos)
{
sio_putchar(c);
return 1;
}
int sio_vprintf(const char *fmt, va_list ap)
{
return __vsnprintf_internal(NULL, -1, fmt, ap, sio_put_in_string);
}
static int out_put_in_string(char *string, unsigned int sz, char c, int pos)
{
putchar(c);
return 1;
}
int vprintf(const char *fmt, va_list ap)
{
return __vsnprintf_internal(NULL, -1, fmt, ap, out_put_in_string);
}
int vsprintf(char *string, const char *fmt, va_list ap)
{
return vsnprintf(string, 0xffffffff, fmt, ap);
}
int sprintf(char *string, const char *fmt, ...)
{
int r;
va_list ap;
va_start(ap, fmt);
r = vsprintf(string, fmt, ap);
va_end(ap);
return r;
}
int snprintf(char *string, size_t size, const char *fmt, ...)
{
int r;
va_list ap;
va_start(ap, fmt);
r = vsnprintf(string, size, fmt, ap);
va_end(ap);
return r;
}
int sio_printf(const char *fmt, ...)
{
int r;
va_list ap;
va_start(ap, fmt);
r = sio_vprintf(fmt, ap);
va_end(ap);
return r;
}