Implement a much more capable vuprintf()

New support as well as some buggy support fixed.

Still no floating point support if ever that would be desired.

Support (*):
* Flags: '-', '+', ' ', '#', '0'

* Width and precision: 'n', '.n', '*' and '.*'

* Length modifiers: 'hh', 'h', 'j', 'l', 'll', 't', 'z'

* Radix: 'c', 'd', 'i', 'n', 'o', 'p/P', 's', 'u', 'x/X'

(*) Provision exists to switch lesser-used stuff on or off or when
certain functionality isn't desired (bootloader?). The compulsory
radixes are everything but 'o', 'n', 'p/P' and 'x/X' with length
modifiers being optional. The default setup is 'l', 'z', 'c', 'd',
'p/P', 's', 'u', 'x/X'.

* Move fdprintf() to its own file. It was in a strange place.

* Make callers compatible and fix a couple snprintf() bugs while
at it.

Could smush it down in size but I'm gonna get over the binsize
neurosis and just the let optimizer do its thing.

Change-Id: Ibdc613a9b6775802c188b29b9dd46c568c94f7c3
This commit is contained in:
Michael Sevakis 2017-09-08 19:28:02 -04:00
parent 52af55eee8
commit 5c9688961e
8 changed files with 1129 additions and 337 deletions

View File

@ -204,7 +204,7 @@ common/fileobj_mgr.c
common/dircache.c common/dircache.c
#endif /* HAVE_DIRCACHE */ #endif /* HAVE_DIRCACHE */
common/pathfuncs.c common/pathfuncs.c
common/format.c common/fdprintf.c
common/linked_list.c common/linked_list.c
common/strcasecmp.c common/strcasecmp.c
common/strcasestr.c common/strcasestr.c
@ -214,6 +214,7 @@ common/strlcpy.c
common/structec.c common/structec.c
common/timefuncs.c common/timefuncs.c
common/unicode.c common/unicode.c
common/vuprintf.c
/* Display */ /* Display */
scroll_engine.c scroll_engine.c

View File

@ -0,0 +1,56 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2002 by Felix Arends
*
* 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
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include <limits.h>
#include "file.h"
#include "vuprintf.h"
struct for_fprintf {
int fd; /* where to store it */
int rem; /* amount remaining */
};
static int fprfunc(void *pr, int letter)
{
struct for_fprintf *fpr = (struct for_fprintf *)pr;
/* TODO: add a small buffer to reduce write() calls */
if (write(fpr->fd, &(char){ letter }, 1) > 0) {
return --fpr->rem;
}
return -1;
}
int fdprintf(int fd, const char *fmt, ...)
{
int bytes;
struct for_fprintf fpr;
va_list ap;
fpr.fd = fd;
fpr.rem = INT_MAX;
va_start(ap, fmt);
bytes = vuprintf(fprfunc, &fpr, fmt, ap);
va_end(ap);
return bytes;
}

View File

@ -1,267 +0,0 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2002 by Gary Czvitkovicz
*
* 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
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include <stdarg.h>
#include <stdbool.h>
#include <limits.h>
#include <string.h>
#include "file.h"
#include "format.h"
static const char hexdigit[] = "0123456789ABCDEF";
void format(
/* call 'push()' for each output letter */
int (*push)(void *userp, unsigned char data),
void *userp,
const char *fmt,
va_list ap)
{
char *str;
char tmpbuf[12], pad;
int ch, width, val, sign, precision;
long lval, lsign;
unsigned int uval;
unsigned long ulval;
size_t uszval;
ssize_t szval, szsign;
bool ok = true;
tmpbuf[sizeof tmpbuf - 1] = '\0';
while ((ch = *fmt++) != '\0' && ok)
{
if (ch == '%')
{
ch = *fmt++;
pad = ' ';
if (ch == '0')
pad = '0';
width = 0;
while (ch >= '0' && ch <= '9')
{
width = 10*width + ch - '0';
ch = *fmt++;
}
precision = 0;
if(ch == '.')
{
ch = *fmt++;
while (ch >= '0' && ch <= '9')
{
precision = 10*precision + ch - '0';
ch = *fmt++;
}
} else {
precision = INT_MAX;
}
str = tmpbuf + sizeof tmpbuf - 1;
switch (ch)
{
case 'c':
*--str = va_arg (ap, int);
break;
case 's':
str = va_arg (ap, char*);
break;
case 'd':
val = sign = va_arg (ap, int);
if (val < 0)
val = -val;
do
{
*--str = (val % 10) + '0';
val /= 10;
}
while (val > 0);
if (sign < 0)
*--str = '-';
break;
case 'u':
uval = va_arg(ap, unsigned int);
do
{
*--str = (uval % 10) + '0';
uval /= 10;
}
while (uval > 0);
break;
case 'p':
case 'P':
/* for pointers prepend 0x and act like 'X' */
push(userp, '0');
push(userp, 'x');
/* fall through */
case 'x':
case 'X':
pad='0';
uval = va_arg (ap, int);
do
{
*--str = hexdigit[uval & 0xf];
uval >>= 4;
}
while (uval);
break;
case 'l':
ch = *fmt++;
switch(ch) {
case 'x':
case 'X':
pad='0';
ulval = va_arg (ap, long);
do
{
*--str = hexdigit[ulval & 0xf];
ulval >>= 4;
}
while (ulval);
break;
case 'd':
lval = lsign = va_arg (ap, long);
if (lval < 0)
lval = -lval;
do
{
*--str = (lval % 10) + '0';
lval /= 10;
}
while (lval > 0);
if (lsign < 0)
*--str = '-';
break;
case 'u':
ulval = va_arg(ap, unsigned long);
do
{
*--str = (ulval % 10) + '0';
ulval /= 10;
}
while (ulval > 0);
break;
default:
*--str = 'l';
*--str = ch;
}
break;
case 'z':
ch = *fmt++;
switch(ch) {
case 'd':
szval = szsign = va_arg (ap, ssize_t);
if (szval < 0)
szval = -szval;
do
{
*--str = (szval % 10) + '0';
szval /= 10;
}
while (szval > 0);
if (szsign < 0)
*--str = '-';
break;
case 'u':
uszval = va_arg(ap, size_t);
do
{
*--str = (uszval % 10) + '0';
uszval /= 10;
}
while (uszval > 0);
break;
default:
*--str = 'z';
*--str = ch;
}
break;
default:
*--str = ch;
break;
}
if (width > 0)
{
width -= strlen (str);
while (width-- > 0 && ok)
ok=push(userp, pad);
}
while (*str != '\0' && ok && precision--)
ok=push(userp, *str++);
}
else
ok=push(userp, ch);
}
}
struct for_fprintf {
int fd; /* where to store it */
int bytes; /* amount stored */
};
static int fprfunc(void *pr, unsigned char letter)
{
struct for_fprintf *fpr = (struct for_fprintf *)pr;
int rc = write(fpr->fd, &letter, 1);
if(rc > 0) {
fpr->bytes++; /* count them */
return true; /* we are ok */
}
return false; /* failure */
}
int fdprintf(int fd, const char *fmt, ...)
{
va_list ap;
struct for_fprintf fpr;
fpr.fd=fd;
fpr.bytes=0;
va_start(ap, fmt);
format(fprfunc, &fpr, fmt, ap);
va_end(ap);
return fpr.bytes; /* return 0 on error */
}
void vuprintf(int (*push)(void *userp, unsigned char data), void *userp, const char *fmt, va_list ap)
{
format(push, userp, fmt, ap);
}

974
firmware/common/vuprintf.c Normal file
View File

@ -0,0 +1,974 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2002 by Gary Czvitkovicz
* Copyright (C) 2017 by Michael A. Sevakis
*
* 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
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include <sys/types.h>
#include <limits.h>
#include <string.h>
#include "system.h"
#include "vuprintf.h"
#if 0
/* turn everything on */
#define FMT_LENMOD (0xffffffff)
#define FMT_RADIX (0xffffffff)
#endif
/* these are the defaults if no other preference is given */
#ifndef FMT_LENMOD
#define FMT_LENMOD (FMT_LENMOD_l | \
FMT_LENMOD_z)
#endif /* FMT_LENMOD */
#ifndef FMT_RADIX
#define FMT_RADIX (FMT_RADIX_c | \
FMT_RADIX_d | \
FMT_RADIX_p | \
FMT_RADIX_s | \
FMT_RADIX_u | \
FMT_RADIX_x)
#endif /* FMT_RADIX */
/** Length modifier and radix flags **/
/* compulsory length modifiers: NONE
* however a compatible 'l' or 'll' must be defined if another requires it */
#define FMT_LENMOD_h 0x001 /* signed/unsigned short (%h<radix>) */
#define FMT_LENMOD_hh 0x002 /* signed/unsigned char (%hh<radix>) */
#define FMT_LENMOD_j 0x004 /* intmax_t/uintmax_t (%j<radix>) */
#define FMT_LENMOD_l 0x008 /* signed/unsigned long (%l<radix>) */
#define FMT_LENMOD_ll 0x010 /* signed/unsigned long long (%ll<radix>) */
#define FMT_LENMOD_t 0x020 /* signed/unsigned ptrdiff_t (%t<radix>) */
#define FMT_LENMOD_z 0x040 /* size_t/ssize_t (%z<radix>) */
#define FMT_LENMOD_L 0x080 /* long double (instead of double) */
/* compulsory radixes: c, d, i, u, s */
#define FMT_RADIX_c 0x001 /* single character (%c) */
#define FMT_RADIX_d 0x002 /* signed integer type, decimal (%d %i) */
#define FMT_RADIX_n 0x004 /* bytes output so far (%n) */
#define FMT_RADIX_o 0x008 /* unsigned integer type, octal (%o) */
#define FMT_RADIX_p 0x010 /* pointer (%p %P) */
#define FMT_RADIX_s 0x020 /* string (%s) */
#define FMT_RADIX_u 0x040 /* unsigned integer type, decimal (%u) */
#define FMT_RADIX_x 0x080 /* unsigned integer type, hex (%x %X) */
#define FMT_RADIX_a 0x100 /* hex floating point "[-]0xh.hhhhp±d" */
#define FMT_RADIX_e 0x200 /* floating point with exponent "[-]d.ddde±dd" */
#define FMT_RADIX_f 0x400 /* floating point "[-]ddd.ddd" */
#define FMT_RADIX_g 0x800 /* floating point exponent or decimal depending
upon value and precision */
/* avoid defining redundant functions if two or more types can use the same
* something not getting a macro means it gets assigned its own value and
* formatter */
/* l */
#if LONG_MIN == INT_MIN && LONG_MAX == INT_MAX
#define val_ld val_d
#define format_ld format_d
#define branch_fmt_ld branch_fmt_d
#elif !(FMT_LENMOD & FMT_LENMOD_l) /* unique */
#define val_ld
#endif /* LONG_ */
#if ULONG_MAX == UINT_MAX
#define val_lu val_u
#define format_lu format_u
#define branch_fmt_lu branch_fmt_u
#elif !(FMT_LENMOD & FMT_LENMOD_l) /* unique */
#define val_lu
#endif /* ULONG_ */
/* ll */
#if LLONG_MIN == INT_MIN && LLONG_MAX == INT_MAX
#define val_lld val_d
#define format_lld format_d
#define branch_fmt_lld branch_fmt_d
#elif LLONG_MIN == LONG_MIN && LLONG_MAX == LONG_MAX
#define val_lld val_ld
#define format_lld format_ld
#define branch_fmt_lld branch_fmt_ld
#elif !(FMT_LENMOD & FMT_LENMOD_ll) /* unique */
#define val_lld
#endif /* LLONG_ */
#if ULLONG_MAX == UINT_MAX
#define val_llu val_u
#define format_llu format_u
#define branch_fmt_llu branch_fmt_u
#elif ULLONG_MAX == ULONG_MAX
#define val_llu val_lu
#define format_llu format_lu
#define branch_fmt_llu branch_fmt_lu
#elif !(FMT_LENMOD & FMT_LENMOD_ll) /* unique */
#define val_llu
#endif /* ULLONG_ */
/* char/short parameter type promotions */
#define SCHAR_INT_ARG int
#define UCHAR_INT_ARG int
#define SSHRT_INT_ARG int
#if USHRT_MAX == UINT_MAX
#define USHRT_INT_ARG unsigned int
#else
#define USHRT_INT_ARG int
#endif
/* some macros to have conditional work inside macros */
#if (FMT_LENMOD & FMT_LENMOD_l)
#define IF_FMT_LENMOD_l(...) __VA_ARGS__
#else
#define IF_FMT_LENMOD_l(...)
#endif
#if (FMT_LENMOD & FMT_LENMOD_ll)
#define IF_FMT_LENMOD_ll(...) __VA_ARGS__
#else
#define IF_FMT_LENMOD_ll(...)
#endif
#if (FMT_RADIX & FMT_RADIX_o)
#define IF_FMT_RADIX_o(...) __VA_ARGS__
#else
#define IF_FMT_RADIX_o(...)
#endif
#if (FMT_RADIX & FMT_RADIX_x)
#define IF_FMT_RADIX_x(...) __VA_ARGS__
#else
#define IF_FMT_RADIX_x(...)
#endif
/* synthesize multicharacter constant */
#define LENMOD2(cv, ch) \
(((cv) << CHAR_BIT) | (ch))
#define LENMOD_NONE 0
#if (FMT_LENMOD & FMT_LENMOD_h)
#define LENMOD_h 'h'
#endif
#if (FMT_LENMOD & FMT_LENMOD_hh)
#define LENMOD_hh LENMOD2('h', 'h') /* 'hh' */
#endif
#if (FMT_LENMOD & FMT_LENMOD_j)
#define LENMOD_j 'j'
#endif
#if (FMT_LENMOD & FMT_LENMOD_l)
#define LENMOD_l 'l'
#endif
#if (FMT_LENMOD & FMT_LENMOD_ll)
#undef FMT_MAX_L
#define LENMOD_ll LENMOD2('l', 'l') /* 'll' */
#endif
#if (FMT_LENMOD & FMT_LENMOD_t)
#define LENMOD_t 't'
#endif
#if (FMT_LENMOD & FMT_LENMOD_z)
#define LENMOD_z 'z'
#endif
/* select type-compatible length modifier
* (a bit hacky; it should be range-based) */
#define LENMOD_INTCOMPAT_SEL(type, signd) \
({ int __lenmod; \
size_t __size = sizeof (type); \
if (__size == ((signd) ? sizeof (int) : \
sizeof (unsigned int))) { \
__lenmod = LENMOD_NONE; \
} \
else if (__size == ((signd) ? sizeof (long) : \
sizeof (unsigned long))) { \
IF_FMT_LENMOD_l(__lenmod = LENMOD_l;) \
} \
else if (__size == ((signd) ? sizeof (long long) : \
sizeof (unsigned long long))) { \
IF_FMT_LENMOD_ll(__lenmod = LENMOD_ll;) \
} \
__lenmod; })
/* call formatting function for the compatible integer type */
#define LENMOD_INTCOMPAT_CALL(inteqv, val, fmt_buf, x, signd) \
({ const char *__buf; \
switch (inteqv) { \
case LENMOD_NONE: \
__buf = (signd) ? \
format_d((val), (fmt_buf), (x)) : \
format_u((val), (fmt_buf), (x)); \
break; \
IF_FMT_LENMOD_l( \
case LENMOD_l: \
__buf = (signd) ? \
format_ld((val), (fmt_buf), (x)) : \
format_lu((val), (fmt_buf), (x)); \
break; \
) \
IF_FMT_LENMOD_ll( \
case LENMOD_ll: \
__buf = (signd) ? \
format_lld((val), (fmt_buf), (x)) : \
format_llu((val), (fmt_buf), (x)); \
break; \
) \
} \
__buf; \
})
/* execute formatting branch for the compatible integer type */
#define LENMOD_INTCOMPAT_BRANCH(inteqv, val, signd) \
({ switch (inteqv) { \
case LENMOD_NONE: \
if (signd) { \
val_d = (val); \
goto branch_fmt_d; \
} \
else { \
val_u = (val); \
goto branch_fmt_u; \
} \
IF_FMT_LENMOD_l( \
case LENMOD_l: \
if (signd) { \
val_ld = (val); \
goto branch_fmt_ld; \
} \
else { \
val_lu = (val); \
goto branch_fmt_lu; \
} \
) \
IF_FMT_LENMOD_ll( \
case LENMOD_ll: \
if (signd) { \
val_lld = (val); \
goto branch_fmt_lld; \
} \
else { \
val_llu = (val); \
goto branch_fmt_llu; \
} \
) \
} \
})
#define CONVERT_RADIX_10_SIGN(val, fmt_buf, p, signchar, type) \
do { \
if (val) { \
unsigned type v; \
\
if (val < 0) { \
v = (typeof (v))-(val + 1) + 1; \
signchar = '-'; \
} \
else { \
v = val; \
} \
\
do { \
*--p = (v % 10) + '0'; \
v /= 10; \
} while (v); \
} \
\
if (signchar) { \
p[-1] = signchar; \
fmt_buf->length = 1; \
break; \
} \
\
fmt_buf->length = 0; \
} while (0)
#define CONVERT_RADIX_8(val, fmt_buf, p) \
do { \
if (val) { \
typeof (val) v = val; \
\
do { \
*--p = (v % 010) + '0'; \
v /= 010; \
} while (v); \
} \
\
if (fmt_buf->length) { \
*--p = '0'; \
fmt_buf->length = 0; \
} \
} while (0)
#define CONVERT_RADIX_10(val, fmt_buf, p) \
do { \
if (val) { \
typeof (val) v = val; \
\
do { \
*--p = (v % 10) + '0'; \
v /= 10; \
} while (v); \
} \
\
fmt_buf->length = 0; \
} while (0)
#define CONVERT_RADIX_16(val, fmt_buf, p, x) \
do { \
if (val) { \
const int h = x - 'X' - 0xA \
+ 'A' - '0'; \
typeof (val) v = val; \
\
do { \
unsigned int d = v % 0x10; \
if (d >= 0xA) { \
d += h; \
} \
*--p = d + '0'; \
v /= 0x10; \
} while (v); \
\
if (fmt_buf->length) { \
p[-1] = x; \
p[-2] = '0'; \
fmt_buf->length = 2; \
break; \
} \
} \
\
fmt_buf->length = 0; \
} while (0)
#define CONVERT_RADIX_NOSIGN(val, fmt_buf, p, x) \
switch (x) \
{ \
IF_FMT_RADIX_o( case 'o': \
CONVERT_RADIX_8(val, fmt_buf, p); \
break; ) \
case 'u': \
CONVERT_RADIX_10(val, fmt_buf, p); \
break; \
IF_FMT_RADIX_x( default: \
CONVERT_RADIX_16(val, fmt_buf, p, x); \
break; ) \
}
struct fmt_buf {
const char *fmt_start; /* second character of formatter after '%' */
size_t length; /* length of formatted text (non-numeric)
or prefix (numeric) */
char buf[24]; /* work buffer */
char bufend[1]; /* buffer end marker and guard '0' */
};
/* %d %i */
static inline const char * format_d(int val,
struct fmt_buf *fmt_buf,
int signchar)
{
char *p = fmt_buf->bufend;
CONVERT_RADIX_10_SIGN(val, fmt_buf, p, signchar, int);
return p;
}
/* %o %u %x %X */
static inline const char * format_u(unsigned int val,
struct fmt_buf *fmt_buf,
int radixchar)
{
char *p = fmt_buf->bufend;
CONVERT_RADIX_NOSIGN(val, fmt_buf, p, radixchar);
return p;
}
#if (FMT_LENMOD & FMT_LENMOD_l)
#ifndef format_ld
/* %ld %li */
static inline const char * format_ld(long val,
struct fmt_buf *fmt_buf,
int signchar)
{
char *p = fmt_buf->bufend;
CONVERT_RADIX_10_SIGN(val, fmt_buf, p, signchar, long);
return p;
}
#endif /* format_ld */
#ifndef format_lu
/* %lo %lu %lx %lX */
static inline const char * format_lu(unsigned long val,
struct fmt_buf *fmt_buf,
int radixchar)
{
char *p = fmt_buf->bufend;
CONVERT_RADIX_NOSIGN(val, fmt_buf, p, radixchar);
return p;
}
#endif /* format_lu */
#endif /* FMT_LENMOD_l */
#if (FMT_LENMOD & FMT_LENMOD_ll)
#ifndef format_lld
/* %lld %lli */
static inline const char * format_lld(long long val,
struct fmt_buf *fmt_buf,
int signchar)
{
char *p = fmt_buf->bufend;
CONVERT_RADIX_10_SIGN(val, fmt_buf, p, signchar, long long);
return p;
}
#endif /* format_lld */
#ifndef format_llu
/* %llo %llu %llx %llX */
static inline const char * format_llu(unsigned long long val,
struct fmt_buf *fmt_buf,
int radixchar)
{
char *p = fmt_buf->bufend;
CONVERT_RADIX_NOSIGN(val, fmt_buf, p, radixchar);
return p;
}
#endif /* format_llu */
#endif /* FMT_LENMOD_ll */
/* %c */
static inline const char * format_c(int c,
struct fmt_buf *fmt_buf,
int lenmod)
{
if (lenmod != LENMOD_NONE) {
return NULL; /* wchar_t support for now */
}
char *p = fmt_buf->bufend;
fmt_buf->length = 1;
*--p = (unsigned char)c;
return p;
}
/* %s */
static inline const char * format_s(const void *str,
struct fmt_buf *fmt_buf,
int precision,
int lenmod)
{
if (lenmod != LENMOD_NONE) {
return NULL; /* wchar_t support for now */
}
/* string length may be specified by precision instead of \0-
terminated; however, don't go past a \0 if one is there */
const char *s = str;
size_t len = precision >= 0 ? precision : -1;
const char *nil = memchr(s, '\0', len);
if (nil) {
len = nil - s;
}
fmt_buf->length = len;
return s;
}
#if (FMT_RADIX & FMT_RADIX_n)
/* %n */
static inline bool format_n(void *np,
int count,
int lenmod)
{
if (lenmod != LENMOD_NONE) {
return false; /* int only for now */
}
*(int *)np = count;
return true;
}
#endif /* FMT_RADIX_n */
#if (FMT_RADIX & FMT_RADIX_p)
/* %p %P */
static inline const char * format_p(const void *p,
struct fmt_buf *fmt_buf,
int radixchar,
bool *numericp)
{
if (p) {
/* format as %#x or %#X */
*numericp = true;
radixchar -= 'P' - 'X';
fmt_buf->length = 2;
return LENMOD_INTCOMPAT_CALL(LENMOD_INTCOMPAT_SEL(uintptr_t, false),
(uintptr_t)p, fmt_buf, radixchar, false);
}
else {
/* format as %s */
fmt_buf->length = 5;
return "(nil)";
}
}
#endif /* FMT_RADIX_p */
/* parse fixed width or precision field */
static const char * parse_number_spec(const char *fmt,
int ch,
int *out)
{
int i = ch - '0';
while (1) {
ch = *fmt - '0';
if (ch < 0 || ch > 9 || i > INT_MAX / 10 ||
(i == INT_MAX / 10 && ch > INT_MAX % 10)) {
break;
}
i = i * 10 + ch;
fmt++;
}
*out = i;
return fmt;
}
int vuprintf(vuprintf_push_cb push, /* call 'push()' for each output letter */
void *userp,
const char *fmt,
va_list ap)
{
#define PUSHCHAR(ch) \
do { \
int __ch = (ch); \
int __rc = push(userp, __ch); \
count += __rc >= 0; \
if (__rc <= 0) { \
goto done; \
} \
} while (0)
int count = 0;
int ch;
/* macrofied identifiers share a variable with another */
unsigned int val_d;
unsigned int val_u;
#ifndef val_ld
unsigned long val_ld;
#endif
#ifndef val_lu
unsigned long val_lu;
#endif
#ifndef val_lld
unsigned long long val_lld;
#endif
#ifndef val_llu
unsigned long long val_llu;
#endif
struct fmt_buf fmt_buf;
fmt_buf.bufend[0] = '0';
while (1) {
while (1) {
if ((ch = *fmt++) == '\0') {
goto done;
}
if (ch == '%' && (ch = *fmt++) != '%') {
break;
}
PUSHCHAR(ch);
}
/* set to defaults */
fmt_buf.fmt_start = fmt;
int signchar = 0;
unsigned int width = 0;
int lenmod = LENMOD_NONE;
size_t length = 0;
size_t pfxlen = 0;
bool numeric = false;
int alignchar = '0' + 1;
int precision = -1;
const char *buf = NULL;
/*** flags ***/
while (1) {
switch (ch)
{
case ' ': /* <space> before non-negative value (signed conversion) */
case '+': /* '+' before non-negative value (signed conversion) */
/* '+' overrides ' ' */
if (ch > signchar) {
signchar = ch;
}
break;
case '-': /* left-justify in field */
case '0': /* zero-pad to fill field */
/* '-' overrides '0' */
if (ch < alignchar) {
alignchar = ch;
}
break;
case '#': /* number prefix (nonzero %o:'0' %x/%X:'0x') */
/* indicate; formatter updates with actual length */
pfxlen = 1;
break;
#if 0
case '\'': /* digit grouping (non-monetary) */
break;
#endif
default:
goto flags_done;
}
ch = *fmt++;
}
flags_done:
/*** width ***/
if (ch == '*') {
/* variable width */
int w = va_arg(ap, int);
if (w < 0) {
/* negative width is width with implied '-' */
width = (unsigned int)-(w + 1) + 1;
alignchar = '-';
}
else {
width = w;
}
ch = *fmt++;
}
else if (ch >= '1' && ch <= '9') {
/* fixed width */
fmt = parse_number_spec(fmt, ch, &width);
ch = *fmt++;
}
/*** precision ***/
if (ch == '.') {
ch = *fmt++;
if (ch == '*') {
/* variable precision; negative precision is ignored */
precision = va_arg (ap, int);
ch = *fmt++;
}
else if (ch >= '0' && ch <= '9') {
/* fixed precision */
fmt = parse_number_spec(fmt, ch, &precision);
ch = *fmt++;
}
}
/*** length modifier ***/
#if FMT_LENMOD
switch (ch)
{
#if (FMT_LENMOD & (FMT_LENMOD_h | FMT_LENMOD_hh))
case 'h':
#endif
#if (FMT_LENMOD & FMT_LENMOD_j)
case 'j':
#endif
#if (FMT_LENMOD & (FMT_LENMOD_l | FMT_LENMOD_ll))
case 'l':
#endif
#if (FMT_LENMOD & FMT_LENMOD_t)
case 't':
#endif
#if (FMT_LENMOD & FMT_LENMOD_z)
case 'z':
#endif
lenmod = ch;
ch = *fmt++;
#if (FMT_LENMOD & (FMT_LENMOD_hh | FMT_LENMOD_ll))
/* doesn't matter if jj, tt or zz happen; they will be rejected
by the radix handler */
if (ch == lenmod) {
lenmod = LENMOD2(lenmod, ch);
ch = *fmt++;
}
#endif
}
#endif /* FMT_LENMOD */
/*** radix ***/
switch (ch)
{
/** non-numeric **/
case 'c':
buf = format_c(va_arg(ap, int), &fmt_buf, lenmod);
break;
#if (FMT_RADIX & FMT_RADIX_n)
case 'n':
if (format_n(va_arg(ap, void *), count, lenmod)) {
continue; /* no output */
}
break;
#endif
case 's':
buf = format_s(va_arg(ap, const void *), &fmt_buf,
precision, lenmod);
break;
/** non-integer **/
#if (FMT_RADIX & FMT_RADIX_p)
case 'p':
case 'P':
buf = format_p(va_arg(ap, void *), &fmt_buf, ch,
&numeric);
break;
#endif
/** signed integer **/
case 'd':
case 'i':
fmt_buf.length = pfxlen;
switch (lenmod)
{
case LENMOD_NONE:
val_d = va_arg(ap, signed int);
goto branch_fmt_d;
#if (FMT_LENMOD & FMT_LENMOD_h)
case LENMOD_h:
val_d = (signed short)va_arg(ap, SSHRT_INT_ARG);
goto branch_fmt_d;
#endif
#if (FMT_LENMOD & FMT_LENMOD_hh)
case LENMOD_hh:
val_d = (signed char)va_arg(ap, SCHAR_INT_ARG);
goto branch_fmt_d;
#endif
#if (FMT_LENMOD & FMT_LENMOD_j)
case LENMOD_j:
LENMOD_INTCOMPAT_BRANCH(LENMOD_INTCOMPAT_SEL(intmax_t, true),
va_arg(ap, intmax_t), true);
#endif
#if (FMT_LENMOD & FMT_LENMOD_l)
case LENMOD_l:
val_ld = va_arg(ap, signed long);
goto branch_fmt_ld;
#endif
#if (FMT_LENMOD & FMT_LENMOD_ll)
case LENMOD_ll:
val_lld = va_arg(ap, signed long long);
goto branch_fmt_lld;
#endif
#if (FMT_LENMOD & FMT_LENMOD_t)
case LENMOD_t:
LENMOD_INTCOMPAT_BRANCH(LENMOD_INTCOMPAT_SEL(ptrdiff_t, true),
va_arg(ap, ptrdiff_t), true);
#endif
#if (FMT_LENMOD & FMT_LENMOD_z)
case LENMOD_z:
LENMOD_INTCOMPAT_BRANCH(LENMOD_INTCOMPAT_SEL(ssize_t, true),
va_arg(ap, ssize_t), true);
#endif
}
/* macrofied labels share a formatter with another */
if (0) {
branch_fmt_d:
buf = format_d(val_d, &fmt_buf, signchar);
} else if (0) {
#ifndef val_ld
branch_fmt_ld:
buf = format_ld(val_ld, &fmt_buf, signchar);
#endif
} else if (0) {
#ifndef val_lld
branch_fmt_lld:
buf = format_lld(val_lld, &fmt_buf, signchar);
#endif
}
numeric = true;
break;
/** unsigned integer **/
#if (FMT_RADIX & FMT_RADIX_o)
case 'o':
#endif
case 'u':
#if (FMT_RADIX & FMT_RADIX_x)
case 'x':
case 'X':
#endif
fmt_buf.length = pfxlen;
switch (lenmod)
{
case LENMOD_NONE:
val_u = va_arg(ap, unsigned int);
goto branch_fmt_u;
#if (FMT_LENMOD & FMT_LENMOD_h)
case LENMOD_h:
val_u = (unsigned short)va_arg(ap, USHRT_INT_ARG);
goto branch_fmt_u;
#endif
#if (FMT_LENMOD & FMT_LENMOD_hh)
case LENMOD_hh:
val_u = (unsigned char)va_arg(ap, UCHAR_INT_ARG);
goto branch_fmt_u;
#endif
#if (FMT_LENMOD & FMT_LENMOD_j)
case LENMOD_j:
LENMOD_INTCOMPAT_BRANCH(LENMOD_INTCOMPAT_SEL(uintmax_t, false),
va_arg(ap, uintmax_t), false);
#endif
#if (FMT_LENMOD & FMT_LENMOD_l)
case LENMOD_l:
val_lu = va_arg(ap, unsigned long);
goto branch_fmt_lu;
#endif
#if (FMT_LENMOD & FMT_LENMOD_ll)
case LENMOD_ll:
val_llu = va_arg(ap, unsigned long long);
goto branch_fmt_llu;
#endif
#if (FMT_LENMOD & (FMT_LENMOD_t | FMT_LENMOD_z))
/* format "uptrdiff_t" as size_t (unless it becomes standard) */
#if (FMT_LENMOD & FMT_LENMOD_t)
case LENMOD_t:
#endif
#if (FMT_LENMOD & FMT_LENMOD_z)
case LENMOD_z:
#endif
LENMOD_INTCOMPAT_BRANCH(LENMOD_INTCOMPAT_SEL(size_t, false),
va_arg(ap, size_t), false);
#endif
}
/* macrofied labels share a formatter with another */
if (0) {
branch_fmt_u:
buf = format_u(val_u, &fmt_buf, ch);
} else if (0) {
#ifndef val_lu
branch_fmt_lu:
buf = format_lu(val_lu, &fmt_buf, ch);
#endif
} else if (0) {
#ifndef val_llu
branch_fmt_llu:
buf = format_llu(val_llu, &fmt_buf, ch);
#endif
}
numeric = true;
break;
}
if (buf) {
/** padding **/
if (numeric) {
/* numeric formats into fmt_buf.buf */
pfxlen = fmt_buf.length;
length = fmt_buf.bufend - buf;
size_t size = pfxlen + length;
if (precision >= 0) {
/* explicit precision */
precision -= (int)length;
if (precision > 0) {
size += precision;
}
width -= MIN(width, size);
}
else {
/* default precision */
if (!length) {
length = 1;
size++;
}
width -= MIN(width, size);
if (alignchar == '0') {
/* width zero-fill */
precision = width;
width = 0;
}
}
}
else {
/* non-numeric: supress prefix and precision; keep length and
width */
pfxlen = 0;
precision = 0;
length = fmt_buf.length;
width -= MIN(width, length);
}
}
else {
/* format not accepted; print it literally */
buf = fmt_buf.fmt_start - 2;
length = fmt - buf;
width = 0;
pfxlen = 0;
precision = 0;
}
/** push all the stuff **/
if (alignchar != '-') {
/* left padding */
while (width > 0) {
PUSHCHAR(' ');
width--;
}
}
/* prefix */
while (pfxlen > 0) {
PUSHCHAR(buf[-pfxlen]);
pfxlen--;
}
/* 0-padding */
while (precision > 0) {
PUSHCHAR('0');
precision--;
}
/* field */
while (length > 0) {
PUSHCHAR(*buf++);
length--;
}
/* right padding */
while (width > 0) {
PUSHCHAR(' ');
width--;
}
}
done:
return count;
}

View File

@ -19,19 +19,27 @@
* *
****************************************************************************/ ****************************************************************************/
#ifndef __FORMAT_H__ #ifndef __VUPRINTF_H__
#define __FORMAT_H__ #define __VUPRINTF_H__
void format( #include <stdarg.h>
/* call 'push()' for each output letter */
int (*push)(void *userp, unsigned char data),
void *userp,
const char *fmt,
va_list ap);
/* callback function is called for every output character (byte) with userp and /* callback function is called for every output character (byte) in the
* should return 0 when ch is a char other than '\0' that should stop printing */ * output with userp
void vuprintf(int (*push)(void *userp, unsigned char data), *
void *userp, const char *fmt, va_list ap); * it must return > 0 to continue (increments counter)
* it may return 0 to stop (increments counter)
* it may return < 0 to stop (does not increment counter)
* a zero in the format string stops (does not increment counter)
*
* caller is reponsible for stopping formatting in order to keep the return
* value from overflowing (assuming they have a reason to care)
*/
typedef int (* vuprintf_push_cb)(void *userp, int c);
#endif /* __FORMAT_H__ */ /*
* returns the number of times push() was called and returned >= 0
*/
int vuprintf(vuprintf_push_cb push, void *userp, const char *fmt, va_list ap);
#endif /* __VUPRINTF_H__ */

View File

@ -18,74 +18,94 @@
* KIND, either express or implied. * KIND, either express or implied.
* *
****************************************************************************/ ****************************************************************************/
/*
* Minimal printf and snprintf formatting functions
*
* These support %c %s %d and %x
* Field width and zero-padding flag only
*/
#include <stdio.h> #include <stdio.h>
#include <stdarg.h>
#include <stdbool.h>
#include <limits.h> #include <limits.h>
#include "format.h" #include <errno.h>
#include "vuprintf.h"
/* ALSA library requires a more advanced snprintf, so let's not /* ALSA library requires a more advanced snprintf, so let's not
override it in simulator for Linux. Note that Cygwin requires override it in simulator for Linux. Note that Cygwin requires
our snprintf or it produces garbled output after a while. */ our snprintf or it produces garbled output after a while. */
struct for_snprintf { struct for_snprintf {
unsigned char *ptr; /* where to store it */ char *ptr; /* where to store it */
size_t bytes; /* amount already stored */ int rem; /* unwritten buffer remaining */
size_t max; /* max amount to store */
}; };
static int sprfunc(void *ptr, unsigned char letter) static int sprfunc(void *ptr, int letter)
{ {
struct for_snprintf *pr = (struct for_snprintf *)ptr; struct for_snprintf *pr = (struct for_snprintf *)ptr;
if(pr->bytes < pr->max) {
*pr->ptr = letter; if (pr->rem > 0) {
pr->ptr++; if (--pr->rem == 0) {
pr->bytes++; return 0;
return true; }
*pr->ptr++ = letter;
return 1;
} }
return false; /* filled buffer */ else {
} if (pr->rem == -INT_MAX) {
pr->rem = 1;
return -1;
}
--pr->rem;
int snprintf(char *buf, size_t size, const char *fmt, ...) return 1;
{ }
va_list ap;
struct for_snprintf pr;
pr.ptr = (unsigned char *)buf;
pr.bytes = 0;
pr.max = size;
va_start(ap, fmt);
format(sprfunc, &pr, fmt, ap);
va_end(ap);
/* make sure it ends with a trailing zero */
pr.ptr[(pr.bytes < pr.max) ? 0 : -1] = '\0';
return pr.bytes;
} }
int vsnprintf(char *buf, size_t size, const char *fmt, va_list ap) int vsnprintf(char *buf, size_t size, const char *fmt, va_list ap)
{ {
struct for_snprintf pr; if (size <= INT_MAX) {
int bytes;
struct for_snprintf pr;
pr.ptr = (unsigned char *)buf; pr.ptr = buf;
pr.bytes = 0; pr.rem = size;
pr.max = size;
format(sprfunc, &pr, fmt, ap); bytes = vuprintf(sprfunc, &pr, fmt, ap);
/* make sure it ends with a trailing zero */ if (size) {
pr.ptr[(pr.bytes < pr.max) ? 0 : -1] = '\0'; *pr.ptr = '\0';
}
return pr.bytes; else if (pr.rem > 0) {
goto overflow;
}
return bytes;
}
overflow:
errno = EOVERFLOW;
return -1;
}
int snprintf(char *buf, size_t size, const char *fmt, ...)
{
if (size <= INT_MAX) {
int bytes;
struct for_snprintf pr;
va_list ap;
pr.ptr = buf;
pr.rem = size;
va_start(ap, fmt);
bytes = vuprintf(sprfunc, &pr, fmt, ap);
va_end(ap);
if (size) {
*pr.ptr = '\0';
}
else if (pr.rem > 0) {
goto overflow;
}
return bytes;
}
overflow:
errno = EOVERFLOW;
return -1;
} }

View File

@ -38,7 +38,7 @@
#endif #endif
#include "logf.h" #include "logf.h"
#include "serial.h" #include "serial.h"
#include "format.h" #include "vuprintf.h"
#ifdef HAVE_USBSTACK #ifdef HAVE_USBSTACK
#include "usb_core.h" #include "usb_core.h"
@ -193,7 +193,7 @@ static void check_logfindex(void)
} }
} }
static int logf_push(void *userp, unsigned char c) static int logf_push(void *userp, int c)
{ {
(void)userp; (void)userp;
@ -210,7 +210,7 @@ static int logf_push(void *userp, unsigned char c)
} }
#endif #endif
return true; return 1;
} }
void _logf(const char *fmt, ...) void _logf(const char *fmt, ...)
@ -310,7 +310,7 @@ void logf_panic_dump(int *y)
#endif #endif
#ifdef ROCKBOX_HAS_LOGDISKF #ifdef ROCKBOX_HAS_LOGDISKF
static int logdiskf_push(void *userp, unsigned char c) static int logdiskf_push(void *userp, int c)
{ {
(void)userp; (void)userp;
@ -319,11 +319,11 @@ static int logdiskf_push(void *userp, unsigned char c)
{ {
strcpy(&logdiskfbuffer[logdiskfindex-8], "LOGFULL"); strcpy(&logdiskfbuffer[logdiskfindex-8], "LOGFULL");
logdiskfindex=MAX_LOGDISKF_SIZE; logdiskfindex=MAX_LOGDISKF_SIZE;
return false; return 0;
} }
logdiskfbuffer[logdiskfindex++] = c; logdiskfbuffer[logdiskfindex++] = c;
return true; return 1;
} }
static void flush_buffer(void); static void flush_buffer(void);

View File

@ -60,7 +60,7 @@ void tx_writec(unsigned char c)
} }
static int uart_push(void *user_data, unsigned char ch) static int uart_push(void *user_data, int ch)
{ {
(void)user_data; (void)user_data;