Files
graphene/Pal/lib/stdlib/printfmt.c

334 lines
6.5 KiB
C

/* Implementation of printf console output for user environments.
*
* printf is a debugging statement, not a generic output statement.
* It is very important that it always go to the console, especially when
* debugging file descriptor code! */
#include <stdint.h>
#include <stdarg.h>
#include "api.h"
// Print a number (base <= 16) in reverse order,
// using specified fputch function and associated pointer putdat.
#if !defined(__i386__)
static int
printnum(int (*_fputch)(void *, int, void *), void * f, void * putdat,
unsigned long long num, unsigned base, int width, int padc)
#else
static int
printnum(int (*_fputch)(void *, int, void *), void * f, void * putdat,
unsigned long num, unsigned base, int width, int padc)
#endif
{
// first recursively print all preceding (more significant) digits
if (num >= base) {
if (printnum(_fputch, f, putdat, num / base, base, width - 1, padc) == -1)
return -1;
} else {
// print any needed pad characters before first digit
while (--width > 0)
if ((*_fputch) (f, padc, putdat) == -1)
return -1;
}
// then print this (the least significant) digit
if ((*_fputch) (f, "0123456789abcdef"[num % base], putdat) == -1)
return -1;
return 0;
}
// Get an unsigned int of various possible sizes from a varargs list,
// depending on the lflag parameter.
#if !defined(__i386__)
inline unsigned long long
getuint(va_list *ap, int lflag)
#else
inline unsigned long
getuint(va_list *ap, int lflag)
#endif
{
#if !defined(__i386__)
if (lflag >= 2)
return va_arg(*ap, unsigned long long);
else
#endif
if (lflag)
return va_arg(*ap, unsigned long);
else
return va_arg(*ap, unsigned int);
}
// Same as getuint but signed - can't use getuint
// because of sign extension
#if !defined(__i386__)
inline long long
getint(va_list *ap, int lflag)
#else
inline long
getint(va_list *ap, int lflag)
#endif
{
#if !defined(__i386__)
if (lflag >= 2)
return va_arg(*ap, long long);
else
#endif
if (lflag)
return va_arg(*ap, long);
else
return va_arg(*ap, int);
}
// Main function to format and print a string.
void fprintfmt(int (*_fputch)(void *, int, void *), void * f, void * putdat,
const char * fmt, ...);
void
vfprintfmt(int (*_fputch)(void *, int, void *), void * f, void * putdat,
const char * fmt, va_list *ap)
{
register const char *p;
register int ch;
#if !defined(__i386__)
unsigned long long num;
#else
unsigned long num;
#endif
int base, lflag, width, precision, altflag;
char padc;
while (1) {
while ((ch = *(unsigned char *) (fmt++)) != '%') {
if (ch == '\0')
return;
if ((*_fputch) (f, ch, putdat) < 0)
return;
}
// Process a %-escape sequence
padc = ' ';
width = -1;
precision = -1;
lflag = 0;
altflag = 0;
reswitch:
switch (ch = *(unsigned char *) (fmt++)) {
// flag to pad on the right
case '-':
padc = ' ';
goto reswitch;
// flag to pad with 0's instead of spaces
case '0':
padc = '0';
goto reswitch;
// width field
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
for (precision = 0; ; ++fmt) {
precision = precision * 10 + ch - '0';
ch = *fmt;
if (ch < '0' || ch > '9')
break;
}
goto process_precision;
case '*':
precision = va_arg(*ap, int);
goto process_precision;
case '.':
if (width < 0)
width = 0;
goto reswitch;
case '#':
altflag = 1;
goto reswitch;
process_precision:
if (width < 0)
width = precision, precision = -1;
goto reswitch;
// long flag (doubled for long long)
case 'l':
lflag++;
goto reswitch;
// character
case 'c':
if ((*_fputch) (f, va_arg(*ap, int), putdat) == -1)
return;
break;
// string
case 's':
if ((p = va_arg(*ap, char *)) == NULL)
p = "(null)";
if (width > 0 && padc != '-')
for (width -= strnlen(p, precision); width > 0; width--)
if ((*_fputch) (f, padc, putdat) == -1)
return;
for (; (ch = *p++) != '\0' && (precision < 0 || --precision >= 0); width--)
if (altflag && (ch < ' ' || ch > '~')) {
if ((*_fputch) (f, '?', putdat) == -1)
return;
} else {
if ((*_fputch) (f, ch, putdat) == -1)
return;
}
for (; width > 0; width--)
if ((*_fputch) (f, ' ', putdat) == -1)
return;
break;
// (signed) decimal
case 'd':
case 'i':
num = getint(ap, lflag);
#if !defined(__i386__)
if ((long long) num < 0) {
if ((*_fputch) (f, '-', putdat) == -1)
return;
num = -(long long) num;
}
#else
if ((long) num < 0) {
if ((*_fputch) (f, '-', putdat) == -1)
return;
num = -(long) num;
}
#endif
base = 10;
goto number;
// unsigned decimal
case 'u':
num = getuint(ap, lflag);
base = 10;
goto number;
// (unsigned) octal
case 'o':
// Replace this with your code.
num = getuint(ap, lflag);
base = 8;
goto number;
// pointer
case 'p':
if ((*_fputch) (f, '0', putdat) == -1)
return;
if ((*_fputch) (f, 'x', putdat) == -1)
return;
#if !defined(__i386__)
num = (unsigned long long)
(uintptr_t) va_arg(*ap, void *);
#else
num = (unsigned long)
(uintptr_t) va_arg(*ap, void *);
#endif
base = 16;
goto number;
// (unsigned) hexadecimal
case 'x':
num = getuint(ap, lflag);
base = 16;
number:
if (printnum(_fputch, f, putdat, num, base, width, padc) == -1)
return;
break;
// escape character
case '^':
if ((*_fputch) (f, 0x1b, putdat) == -1)
return;
break;
// escaped '%' character
case '%':
(*_fputch) (f, ch, putdat);
break;
// unrecognized escape sequence - just print it literally
default:
(*_fputch) (f, '%', putdat);
for (fmt--; fmt[-1] != '%'; fmt--)
/* do nothing */;
break;
}
}
}
void
fprintfmt(int (*_fputch)(void *, int, void *), void * f, void * putdat,
const char * fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintfmt(_fputch, f, putdat, fmt, &ap);
va_end(ap);
}
struct sprintbuf {
int cnt, max;
char * buf;
};
static int
sprintputch(void * f, int ch, struct sprintbuf * b)
{
__UNUSED(f);
if (b->cnt >= b->max)
return -1;
b->buf[b->cnt++] = ch;
return 0;
}
static int
vsprintf(char * buf, int n, const char * fmt, va_list *ap)
{
struct sprintbuf b = { 0, n, buf };
if (!buf || n < 1)
return 0;
// print the string to the buffer
vfprintfmt((void *) sprintputch, (void *) 0, &b, fmt, ap);
// null terminate the buffer
if (b.cnt < n)
b.buf[b.cnt] = '\0';
return b.cnt;
}
int
snprintf(char * buf, int n, const char * fmt, ...)
{
va_list ap;
int rc;
va_start(ap, fmt);
rc = vsprintf(buf, n, fmt, &ap);
va_end(ap);
return rc;
}