rockbox/bootloader/format.c

325 lines
7.6 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2002 by Gary Czvitkovicz
* Copyright (C) 2017 by William Wilgus
*
* 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";
/* smaller compressed binary without inline but 18% slower */
#define FMT_DECL static inline
FMT_DECL int fmt_width_precision(int *ch, const char **fmt, char **str, va_list *ap)
{
int value = 0;
(void) str;
(void) ap;
while (*ch >= '0' && *ch <= '9')
{
value = 10 * value + (*ch - '0');
*ch = *(*fmt)++;
}
return value;
}
FMT_DECL int fmt_integer_signed(int *ch, const char **fmt, char **str, va_list *ap)
{
int val, rem, sign;
(void) ch;
(void) fmt;
val = sign = va_arg(*ap, int);
if (val < 0)
val = -val;
do {
rem = val % 10;
val /= 10;
*--(*str) = rem + '0';
} while (val > 0);
if (sign < 0)
*--(*str) = '-';
return 0;
}
FMT_DECL int fmt_integer_unsigned(int *ch, const char **fmt, char **str, va_list *ap)
{
unsigned int uval, urem;
(void) ch;
(void) fmt;
uval = va_arg(*ap, unsigned int);
do {
urem = uval % 10;
uval /= 10;
*--(*str) = urem + '0';
} while (uval > 0);
return 0;
}
FMT_DECL int fmt_long(int *ch, const char **fmt, char **str, va_list *ap)
{
int pad = 0;
long lval, lrem, lsign = 0;
unsigned long ulval, ulrem;
char ch_l = *ch;
*ch = *(*fmt)++;
if (*ch == 'd') {
lval = lsign = va_arg(*ap, long);
if (lval < 0)
lval = -lval;
do {
lrem = lval % 10;
lval /= 10;
*--(*str) = lrem + '0';
} while (lval > 0);
if (lsign < 0)
*--(*str) = '-';
}
else if (*ch == 'u') {
ulval = va_arg(*ap, unsigned long);
do {
ulrem = ulval % 10;
ulval /= 10;
*--(*str) = ulrem + '0';
} while (ulval > 0);
}
else if (*ch == 'x' || *ch == 'X') {
pad++;
ulval = va_arg(*ap, long);
do {
*--(*str) = hexdigit[ulval & 0xf];
ulval >>= 4;
} while (ulval > 0);
}
else {
*--(*str) = ch_l;
*--(*str) = *ch;
}
return pad;
}
FMT_DECL int fmt_character(int *ch, const char **fmt, char **str, va_list *ap)
{
(void) ch;
(void) fmt;
*--(*str) = va_arg(*ap, int);
return 0;
}
FMT_DECL int fmt_string(int *ch, const char **fmt, char **str, va_list *ap)
{
(void) ch;
(void) fmt;
*str = va_arg (*ap, char*);
return 0;
}
FMT_DECL int fmt_hex_unsigned(int *ch, const char **fmt, char **str, va_list *ap)
{
unsigned int uval;
(void) ch;
(void) fmt;
uval = va_arg(*ap, int);
do {
*--(*str) = hexdigit[uval & 0xf];
uval >>= 4;
} while (uval > 0);
return 1;
}
FMT_DECL int fmt_pointer(int *ch, const char **fmt, char **str, va_list *ap)
{
int pad = fmt_hex_unsigned(ch, fmt, str, ap);
/* for pointers prepend 0x and act like 'X' */
*--(*str) = 'x';
*--(*str) = '0';
return pad;
}
FMT_DECL int fmt_sizet(int *ch, const char **fmt, char **str, va_list *ap)
{
size_t uszval, uszrem;
ssize_t szval, szrem, szsign;
char ch_z = *ch;
*ch = *(*fmt)++;
if (*ch == 'd') {
szval = szsign = va_arg(*ap, ssize_t);
if (szval < 0)
szval = -szval;
do {
szrem = szval % 10;
szval /= 10;
*--(*str) = szrem + '0';
} while (szval > 0);
if (szsign < 0)
*--(*str) = '-';
}
else if (*ch == 'u') {
uszval = va_arg(*ap, size_t);
do {
uszrem = uszval % 10;
uszval /= 10;
*--(*str) = uszrem + '0';
} while (uszval > 0);
}
else {
*--(*str) = ch_z;
*--(*str) = *ch;
}
return 0;
}
static inline int fmt_next_char(int *ch, const char **fmt, char **str, va_list *ap)
{
(void) fmt;
(void) ap;
*--(*str) = *ch;
return 0;
}
void format(
/* call 'push()' for each output letter */
int (*push)(void *userp, unsigned char data),
void *userp,
const char *fmt,
va_list ap)
{
bool ok = true;
char *str;
char tmpbuf[12], pad;
int ch, width, precision, padded;
ch = *fmt++;
tmpbuf[sizeof tmpbuf - 1] = '\0';
do
{
if (ch == '%')
{
str = tmpbuf + sizeof tmpbuf - 1;
ch = *fmt++;
padded = (ch == '0' ? 1 : 0);
width = fmt_width_precision(&ch, &fmt, &str, &ap);
precision = INT_MAX;
if(ch == '.')
{
ch = *fmt++;
precision = fmt_width_precision(&ch, &fmt, &str, &ap);
}
if (ch == 'd')
fmt_integer_signed(&ch, &fmt, &str, &ap);
else if (ch == 'u')
fmt_integer_unsigned(&ch, &fmt, &str, &ap);
else if (ch == 'l')
padded += fmt_long(&ch, &fmt, &str, &ap);
else if (ch == 'c')
fmt_character(&ch, &fmt, &str, &ap);
else if (ch == 's')
fmt_string(&ch, &fmt, &str, &ap);
else if (ch == 'x' || ch == 'X')
padded += fmt_hex_unsigned(&ch, &fmt, &str, &ap);
else if (ch == 'p' || ch == 'P')
padded += fmt_pointer(&ch, &fmt, &str, &ap);
#if 0
else if (ch == 'z')
fmt_sizet(&ch, &fmt, &str, &ap);
#endif
else
fmt_next_char(&ch, &fmt, &str, &ap);
width -= strlen (str);
if (width > 0)
{
pad = (padded ? '0' : ' ');
while (width-- > 0 && ok)
ok=push(userp, pad);
}
while(*str != '\0' && ok && precision--)
ok=push(userp, *str++);
}
else
ok=push(userp, ch);
} while ((ch = *fmt++) != '\0' && ok);
}
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);
}