cLK/lk/lib/libc/printf.c
2011-03-25 23:39:33 +02:00

342 lines
8.0 KiB
C

/*
* Copyright (c) 2008 Travis Geiselbrecht
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <debug.h>
#include <limits.h>
#include <stdarg.h>
#include <sys/types.h>
#include <printf.h>
#include <string.h>
void putc(char c)
{
return _dputc(c);
}
int puts(const char *str)
{
return _dputs(str);
}
int getc(char *c)
{
return dgetc(c);
}
int printf(const char *fmt, ...)
{
int err;
va_list ap;
va_start(ap, fmt);
err = _dvprintf(fmt, ap);
va_end(ap);
return err;
}
int sprintf(char *str, const char *fmt, ...)
{
int err;
va_list ap;
va_start(ap, fmt);
err = vsprintf(str, fmt, ap);
va_end(ap);
return err;
}
int snprintf(char *str, size_t len, const char *fmt, ...)
{
int err;
va_list ap;
va_start(ap, fmt);
err = vsnprintf(str, len, fmt, ap);
va_end(ap);
return err;
}
#define LONGFLAG 0x00000001
#define LONGLONGFLAG 0x00000002
#define HALFFLAG 0x00000004
#define HALFHALFFLAG 0x00000008
#define SIZETFLAG 0x00000010
#define ALTFLAG 0x00000020
#define CAPSFLAG 0x00000040
#define SHOWSIGNFLAG 0x00000080
#define SIGNEDFLAG 0x00000100
#define LEFTFORMATFLAG 0x00000200
#define LEADZEROFLAG 0x00000400
static char *longlong_to_string(char *buf, unsigned long long n, int len, uint flag)
{
int pos = len;
int negative = 0;
if((flag & SIGNEDFLAG) && (long long)n < 0) {
negative = 1;
n = -n;
}
buf[--pos] = 0;
/* only do the math if the number is >= 10 */
while(n >= 10) {
int digit = n % 10;
n /= 10;
buf[--pos] = digit + '0';
}
buf[--pos] = n + '0';
if(negative)
buf[--pos] = '-';
else if((flag & SHOWSIGNFLAG))
buf[--pos] = '+';
return &buf[pos];
}
static char *longlong_to_hexstring(char *buf, unsigned long long u, int len, uint flag)
{
int pos = len;
static const char hextable[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
static const char hextable_caps[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
const char *table;
if((flag & CAPSFLAG))
table = hextable_caps;
else
table = hextable;
buf[--pos] = 0;
do {
unsigned int digit = u % 16;
u /= 16;
buf[--pos] = table[digit];
} while(u != 0);
return &buf[pos];
}
int vsprintf(char *str, const char *fmt, va_list ap)
{
return vsnprintf(str, INT_MAX, fmt, ap);
}
int vsnprintf(char *str, size_t len, const char *fmt, va_list ap)
{
char c;
unsigned char uc;
const char *s;
unsigned long long n;
void *ptr;
int flags;
unsigned int format_num;
size_t chars_written = 0;
char num_buffer[32];
#define OUTPUT_CHAR(c) do { (*str++ = c); chars_written++; if (chars_written + 1 == len) goto done; } while(0)
#define OUTPUT_CHAR_NOLENCHECK(c) do { (*str++ = c); chars_written++; } while(0)
for(;;) {
/* handle regular chars that aren't format related */
while((c = *fmt++) != 0) {
if(c == '%')
break; /* we saw a '%', break and start parsing format */
OUTPUT_CHAR(c);
}
/* make sure we haven't just hit the end of the string */
if(c == 0)
break;
/* reset the format state */
flags = 0;
format_num = 0;
next_format:
/* grab the next format character */
c = *fmt++;
if(c == 0)
break;
switch(c) {
case '0'...'9':
if (c == '0' && format_num == 0)
flags |= LEADZEROFLAG;
format_num *= 10;
format_num += c - '0';
goto next_format;
case '.':
/* XXX for now eat numeric formatting */
goto next_format;
case '%':
OUTPUT_CHAR('%');
break;
case 'c':
uc = va_arg(ap, unsigned int);
OUTPUT_CHAR(uc);
break;
case 's':
s = va_arg(ap, const char *);
if(s == 0)
s = "<null>";
goto _output_string;
case '-':
flags |= LEFTFORMATFLAG;
goto next_format;
case '+':
flags |= SHOWSIGNFLAG;
goto next_format;
case '#':
flags |= ALTFLAG;
goto next_format;
case 'l':
if(flags & LONGFLAG)
flags |= LONGLONGFLAG;
flags |= LONGFLAG;
goto next_format;
case 'h':
if(flags & HALFFLAG)
flags |= HALFHALFFLAG;
flags |= HALFFLAG;
goto next_format;
case 'z':
flags |= SIZETFLAG;
goto next_format;
case 'D':
flags |= LONGFLAG;
/* fallthrough */
case 'i':
case 'd':
n = (flags & LONGLONGFLAG) ? va_arg(ap, long long) :
(flags & LONGFLAG) ? va_arg(ap, long) :
(flags & HALFHALFFLAG) ? (signed char)va_arg(ap, int) :
(flags & HALFFLAG) ? (short)va_arg(ap, int) :
(flags & SIZETFLAG) ? va_arg(ap, ssize_t) :
va_arg(ap, int);
flags |= SIGNEDFLAG;
s = longlong_to_string(num_buffer, n, sizeof(num_buffer), flags);
goto _output_string;
case 'U':
flags |= LONGFLAG;
/* fallthrough */
case 'u':
n = (flags & LONGLONGFLAG) ? va_arg(ap, unsigned long long) :
(flags & LONGFLAG) ? va_arg(ap, unsigned long) :
(flags & HALFHALFFLAG) ? (unsigned char)va_arg(ap, unsigned int) :
(flags & HALFFLAG) ? (unsigned short)va_arg(ap, unsigned int) :
(flags & SIZETFLAG) ? va_arg(ap, size_t) :
va_arg(ap, unsigned int);
s = longlong_to_string(num_buffer, n, sizeof(num_buffer), flags);
goto _output_string;
case 'p':
flags |= LONGFLAG | ALTFLAG;
goto hex;
case 'X':
flags |= CAPSFLAG;
/* fallthrough */
hex:
case 'x':
n = (flags & LONGLONGFLAG) ? va_arg(ap, unsigned long long) :
(flags & LONGFLAG) ? va_arg(ap, unsigned long) :
(flags & HALFHALFFLAG) ? (unsigned char)va_arg(ap, unsigned int) :
(flags & HALFFLAG) ? (unsigned short)va_arg(ap, unsigned int) :
(flags & SIZETFLAG) ? va_arg(ap, size_t) :
va_arg(ap, unsigned int);
s = longlong_to_hexstring(num_buffer, n, sizeof(num_buffer), flags);
if(flags & ALTFLAG) {
OUTPUT_CHAR('0');
OUTPUT_CHAR((flags & CAPSFLAG) ? 'X': 'x');
}
goto _output_string;
case 'n':
ptr = va_arg(ap, void *);
if(flags & LONGLONGFLAG)
*(long long *)ptr = chars_written;
else if(flags & LONGFLAG)
*(long *)ptr = chars_written;
else if(flags & HALFHALFFLAG)
*(signed char *)ptr = chars_written;
else if(flags & HALFFLAG)
*(short *)ptr = chars_written;
else if(flags & SIZETFLAG)
*(size_t *)ptr = chars_written;
else
*(int *)ptr = chars_written;
break;
default:
OUTPUT_CHAR('%');
OUTPUT_CHAR(c);
break;
}
/* move on to the next field */
continue;
/* shared output code */
_output_string:
if (flags & LEFTFORMATFLAG) {
/* left justify the text */
uint count = 0;
while(*s != 0) {
OUTPUT_CHAR(*s++);
count++;
}
/* pad to the right (if necessary) */
for (; format_num > count; format_num--)
OUTPUT_CHAR(' ');
} else {
/* right justify the text (digits) */
size_t string_len = strlen(s);
char outchar = (flags & LEADZEROFLAG) ? '0' : ' ';
for (; format_num > string_len; format_num--)
OUTPUT_CHAR(outchar);
/* output the string */
while(*s != 0)
OUTPUT_CHAR(*s++);
}
continue;
}
done:
/* null terminate */
OUTPUT_CHAR_NOLENCHECK('\0');
chars_written--; /* don't count the null */
#undef OUTPUT_CHAR
#undef OUTPUT_CHAR_NOLENCHECK
return chars_written;
}