348 lines
11 KiB
C
348 lines
11 KiB
C
#include "fslc_stdio.h"
|
|
|
|
/* There'a a funny behaviour of va_list and va_arg() macros:
|
|
* - on x86_64 compiler generates a branch of some sort for each va_arg() macro. Normally it is not a problem,
|
|
* however it kinda screws up branch coverage analysis. Therefore I decided to move va_arg() accesses into
|
|
* dedicated functions, minimizing the issue to one untaken branch per access function.
|
|
* - passing va_list by value works fine on x86_64, but makes some tests to fail on i386. It seems that modifications
|
|
* done by va_arg() is not passed back to caller. Subsequent calls to va_arg() or access functions returns wrong
|
|
* values on that platform.
|
|
* - OK, then. Let's pass pointer to va_list instead. Not so fast. Works fine on i386, but fails to compile on
|
|
* x86_64. Fine! Wrapping va_list into a dedicated struct and passing around pointer to it.
|
|
*
|
|
* Why such a fuss about a few untaken branches in tests? Well, just because! Also, if I ever decide to refactor
|
|
* _fslc_vfprintf_impl() - the case statement is getting a bit long and messy, it should make things easier if
|
|
* argument list can be passed around freely (as opposed to using it from single function).
|
|
*/
|
|
|
|
struct va_list_w
|
|
{
|
|
va_list arg;
|
|
};
|
|
|
|
/* Prototype for printf's internal implementation */
|
|
static int _fslc_vfprintf_impl(FSLC_FILE *stream, const char *format, struct va_list_w *args);
|
|
|
|
|
|
/* Main exported function */
|
|
int fslc_vfprintf(FSLC_FILE *stream, const char *format, va_list arg)
|
|
{
|
|
if (stream->pre_output) stream->pre_output(stream);
|
|
|
|
struct va_list_w argw;
|
|
va_copy(argw.arg, arg); // can not assign directly, should make a copy
|
|
|
|
int res = _fslc_vfprintf_impl(stream, format, &argw);
|
|
|
|
va_end(argw.arg); // and free it after use
|
|
|
|
if (stream->post_output) stream->post_output(stream);
|
|
|
|
return res;
|
|
}
|
|
|
|
/* Prototype to private FSLIBC function - implemented elsewhere */
|
|
int _fslc_fputs_impl(const char *str, FSLC_FILE *stream);
|
|
|
|
|
|
/* Prototypes for internal functions */
|
|
static int _fslc_put_sint_l(signed long v, FSLC_FILE *stream);
|
|
static int _fslc_put_uint_l(unsigned long v, FSLC_FILE *stream);
|
|
static int _fslc_put_hex_l(unsigned long v, FSLC_FILE *stream, char alpha);
|
|
|
|
static int _get_sint_arg(struct va_list_w *arg);
|
|
static unsigned _get_uint_arg(struct va_list_w *arg);
|
|
static long long _get_slonglong_arg(struct va_list_w *arg);
|
|
static unsigned long long _get_ulonglong_arg(struct va_list_w *arg);
|
|
static void *_get_ptr_arg(struct va_list_w *arg);
|
|
|
|
/* There are some considerations when formatting Integers for output:
|
|
* - on 32-bit systems integers will mostly be 32 bits long, except when one REALLY
|
|
* wants to print long long. It should be more optimal to use 32-bit calculations
|
|
* when possible and invoke 64-bit only when needed. To support that we provide 2
|
|
* separate implementations.
|
|
* - on 64-bit systems may be 32 or 64 bits, but there is no real benefit from
|
|
* using shorter bit size. Hence common implementation.
|
|
* - this library does not aim to support systems with shorter integer sizes (like 16-bit)
|
|
* but it it did, it would probably make sense to provide optimized ports for these cases
|
|
* as well.
|
|
*
|
|
* Buffer size calculations:
|
|
* Max UInt32 fits in 10 bytes (+1 for zero terminator) decimal
|
|
* 2**32 - 1 == 4294967295z
|
|
* FFFFFFFFz
|
|
* 01234567890
|
|
*
|
|
* Max UInt64 fits in 10 bytes (and +1 for \0)
|
|
* 2**64 -1 == 18446744073709551615z
|
|
* FFFFFFFFFFFFFFFFz
|
|
* 012345678901234567890
|
|
*/
|
|
|
|
#define BUFSIZE_LONG_LONG_DECIMAL 20
|
|
#define BUFSIZE_LONG_HEX (__SIZEOF_LONG__ * 2)
|
|
#define BUFSIZE_LONG_LONG_HEX (__SIZEOF_LONG_LONG__ * 2)
|
|
#if __SIZEOF_LONG__ == 4
|
|
|
|
#define BUFSIZE_LONG_DECIMAL 10
|
|
static int _fslc_put_sint_ll(signed long long v, FSLC_FILE *stream);
|
|
static int _fslc_put_uint_ll(unsigned long long v, FSLC_FILE *stream);
|
|
static int _fslc_put_hex_ll(unsigned long long v, FSLC_FILE *stream, char alpha);
|
|
|
|
#define _get_slong_arg _get_sint_arg
|
|
#define _get_ulong_arg _get_uint_arg
|
|
|
|
#elif __SIZEOF_LONG__ == 8
|
|
|
|
#define BUFSIZE_LONG_DECIMAL BUFSIZE_LONG_LONG_DECIMAL
|
|
#define _fslc_put_sint_ll _fslc_put_sint_l
|
|
#define _fslc_put_uint_ll _fslc_put_uint_l
|
|
#define _fslc_put_hex_ll _fslc_put_hex_l
|
|
|
|
#define _get_slong_arg _get_slonglong_arg
|
|
#define _get_ulong_arg _get_ulonglong_arg
|
|
|
|
#endif
|
|
|
|
#define FLAG_LONG 1
|
|
#define FLAG_VERYLONG 3
|
|
|
|
static int _fslc_vfprintf_impl(FSLC_FILE *stream, const char *format, struct va_list_w *arg)
|
|
{
|
|
int res = 0;
|
|
int pr;
|
|
const char *c;
|
|
|
|
for (c = format; *c; ++c)
|
|
{
|
|
if (*c == '%')
|
|
{
|
|
int flags = 0;
|
|
|
|
for (;;)
|
|
{
|
|
++c;
|
|
switch (*c)
|
|
{
|
|
case 's':
|
|
pr = _fslc_fputs_impl((const char *)_get_ptr_arg(arg), stream);
|
|
if (pr < 0) return pr;
|
|
res += pr;
|
|
break;
|
|
|
|
case 'c':
|
|
pr = stream->putc(_get_sint_arg(arg), stream);
|
|
if (pr < 0) return pr;
|
|
++res;
|
|
break;
|
|
|
|
case '%':
|
|
pr = stream->putc('%', stream);
|
|
if (pr < 0) return pr;
|
|
++res;
|
|
break;
|
|
|
|
case 'i':
|
|
case 'd':
|
|
if ((flags & FLAG_VERYLONG) == FLAG_VERYLONG)
|
|
pr = _fslc_put_sint_ll(_get_slonglong_arg(arg), stream);
|
|
else if (flags & FLAG_LONG)
|
|
pr = _fslc_put_sint_l(_get_slong_arg(arg), stream);
|
|
else
|
|
pr = _fslc_put_sint_l(_get_sint_arg(arg), stream);
|
|
|
|
if (pr < 0) return pr;
|
|
res += pr;
|
|
break;
|
|
|
|
case 'u':
|
|
if ((flags & FLAG_VERYLONG) == FLAG_VERYLONG)
|
|
pr = _fslc_put_uint_ll(_get_ulonglong_arg(arg), stream);
|
|
else if (flags & FLAG_LONG)
|
|
pr = _fslc_put_uint_l(_get_ulong_arg(arg), stream);
|
|
else
|
|
pr = _fslc_put_uint_l(_get_uint_arg(arg), stream);
|
|
|
|
if (pr < 0) return pr;
|
|
res += pr;
|
|
break;
|
|
|
|
case 'x':
|
|
if ((flags & FLAG_VERYLONG) == FLAG_VERYLONG)
|
|
pr = _fslc_put_hex_ll(_get_ulonglong_arg(arg), stream, 'a');
|
|
else if (flags & FLAG_LONG)
|
|
pr = _fslc_put_hex_l(_get_ulong_arg(arg), stream, 'a');
|
|
else
|
|
pr = _fslc_put_hex_l(_get_uint_arg(arg), stream, 'a');
|
|
|
|
if (pr < 0) return pr;
|
|
res += pr;
|
|
break;
|
|
|
|
case 'X':
|
|
if ((flags & FLAG_VERYLONG) == FLAG_VERYLONG)
|
|
pr = _fslc_put_hex_ll(_get_ulonglong_arg(arg), stream, 'A');
|
|
else if (flags & FLAG_LONG)
|
|
pr = _fslc_put_hex_l(_get_ulong_arg(arg), stream, 'A');
|
|
else
|
|
pr = _fslc_put_hex_l(_get_uint_arg(arg), stream, 'A');
|
|
|
|
if (pr < 0) return pr;
|
|
res += pr;
|
|
break;
|
|
|
|
case 'p':
|
|
pr = _fslc_fputs_impl("0x", stream);
|
|
if (pr < 0) return pr;
|
|
res += pr;
|
|
|
|
pr = _fslc_put_hex_l(_get_ulong_arg(arg), stream, 'a');
|
|
|
|
if (pr < 0) return pr;
|
|
res += pr;
|
|
break;
|
|
|
|
case 'l':
|
|
if (flags & FLAG_LONG)
|
|
flags |= FLAG_VERYLONG;
|
|
else
|
|
flags |= FLAG_LONG;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
} else {
|
|
pr = stream->putc(*c, stream);
|
|
if (pr < 0) return pr;
|
|
++res;
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
static int _get_sint_arg(struct va_list_w *arg)
|
|
{
|
|
return va_arg(arg->arg, signed int);
|
|
}
|
|
|
|
static unsigned _get_uint_arg(struct va_list_w *arg)
|
|
{
|
|
return va_arg(arg->arg, unsigned int);
|
|
}
|
|
|
|
static long long _get_slonglong_arg(struct va_list_w *arg)
|
|
{
|
|
return va_arg(arg->arg, signed long long);
|
|
}
|
|
|
|
static unsigned long long _get_ulonglong_arg(struct va_list_w *arg)
|
|
{
|
|
return va_arg(arg->arg, unsigned long long);
|
|
}
|
|
|
|
static void *_get_ptr_arg(struct va_list_w *arg)
|
|
{
|
|
return va_arg(arg->arg, void *);
|
|
}
|
|
|
|
static int _fslc_put_sint_l(signed long v, FSLC_FILE *stream)
|
|
{
|
|
if (v < 0)
|
|
{
|
|
int pr = stream->putc('-', stream);
|
|
if (pr < 0) return pr;
|
|
|
|
pr = _fslc_put_uint_l(-v, stream);
|
|
if (pr < 0) return pr;
|
|
|
|
return pr+1;
|
|
} else
|
|
return _fslc_put_uint_l(v, stream);
|
|
}
|
|
|
|
static int _fslc_put_uint_l(unsigned long v, FSLC_FILE *stream)
|
|
{
|
|
char dbuff[BUFSIZE_LONG_DECIMAL+1], *p;
|
|
|
|
dbuff[BUFSIZE_LONG_DECIMAL] = 0;
|
|
|
|
for (p = dbuff + BUFSIZE_LONG_DECIMAL; v ; v /= 10)
|
|
{
|
|
*(--p) = (v % 10) + '0';
|
|
}
|
|
|
|
if (*p == 0) *(--p) = '0';
|
|
|
|
return _fslc_fputs_impl(p, stream);
|
|
}
|
|
|
|
static int _fslc_put_hex_l(unsigned long v, FSLC_FILE *stream, char alpha)
|
|
{
|
|
char dbuff[BUFSIZE_LONG_HEX+1], *p;
|
|
|
|
dbuff[BUFSIZE_LONG_HEX] = 0;
|
|
|
|
for (p = dbuff + BUFSIZE_LONG_HEX; v ; v >>= 4)
|
|
{
|
|
char digit = (v & 0xF);
|
|
*(--p) = digit < 10 ? digit + '0' : digit - 10 + alpha;
|
|
}
|
|
|
|
if (*p == 0) *(--p) = '0';
|
|
|
|
return _fslc_fputs_impl(p, stream);
|
|
}
|
|
|
|
#if __SIZEOF_LONG__ == 4
|
|
|
|
static int _fslc_put_sint_ll(signed long long v, FSLC_FILE *stream)
|
|
{
|
|
if (v < 0)
|
|
{
|
|
int pr = stream->putc('-', stream);
|
|
if (pr < 0) return pr;
|
|
|
|
pr = _fslc_put_uint_ll(-v, stream);
|
|
if (pr < 0) return pr;
|
|
|
|
return pr+1;
|
|
} else
|
|
return _fslc_put_uint_ll(v, stream);
|
|
}
|
|
|
|
static int _fslc_put_uint_ll(unsigned long long v, FSLC_FILE *stream)
|
|
{
|
|
char dbuff[BUFSIZE_LONG_LONG_DECIMAL+1], *p;
|
|
|
|
dbuff[BUFSIZE_LONG_LONG_DECIMAL] = 0;
|
|
|
|
for (p = dbuff + BUFSIZE_LONG_LONG_DECIMAL; v ; v /= 10)
|
|
{
|
|
*(--p) = (v % 10) + '0';
|
|
}
|
|
|
|
if (*p == 0) *(--p) = '0';
|
|
|
|
return _fslc_fputs_impl(p, stream);
|
|
}
|
|
|
|
static int _fslc_put_hex_ll(unsigned long long v, FSLC_FILE *stream, char alpha)
|
|
{
|
|
char dbuff[BUFSIZE_LONG_LONG_HEX+1], *p;
|
|
|
|
dbuff[BUFSIZE_LONG_LONG_HEX] = 0;
|
|
|
|
for (p = dbuff + BUFSIZE_LONG_LONG_HEX; v ; v >>= 4)
|
|
{
|
|
char digit = (v & 0xF);
|
|
*(--p) = digit < 10 ? digit + '0' : digit - 10 + alpha;
|
|
}
|
|
|
|
if (*p == 0) *(--p) = '0';
|
|
|
|
return _fslc_fputs_impl(p, stream);
|
|
}
|
|
|
|
#endif
|