forked from Bananymous/banan-os
LibC: add probably functional *printf
I wrote a general printf function that takes an putc function pointer. We can use this to implement all the printf family functions. I haven't done thorough testing with this, but it seems to be functional for the most part
This commit is contained in:
parent
1cf7ef3de6
commit
d5ce4c9d2c
|
@ -5,6 +5,7 @@ project(libc CXX)
|
||||||
set(LIBC_SOURCES
|
set(LIBC_SOURCES
|
||||||
ctype.cpp
|
ctype.cpp
|
||||||
fcntl.cpp
|
fcntl.cpp
|
||||||
|
printf_impl.cpp
|
||||||
stdio.cpp
|
stdio.cpp
|
||||||
stdlib.cpp
|
stdlib.cpp
|
||||||
string.cpp
|
string.cpp
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <sys/cdefs.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
__BEGIN_DECLS
|
||||||
|
|
||||||
|
int printf_impl(const char* format, va_list arguments, int (*putc_fun)(int, void*), void* data);
|
||||||
|
|
||||||
|
__END_DECLS
|
|
@ -0,0 +1,341 @@
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define BAN_PRINTF_PUTC(c) \
|
||||||
|
do \
|
||||||
|
{ \
|
||||||
|
if (putc_fun((c), data) == EOF) \
|
||||||
|
return -1; \
|
||||||
|
written++; \
|
||||||
|
} while (false)
|
||||||
|
|
||||||
|
struct format_options_t
|
||||||
|
{
|
||||||
|
bool alternate_form { false };
|
||||||
|
bool zero_padded { false };
|
||||||
|
bool left_justified { false };
|
||||||
|
bool show_plus_sign_as_space { false };
|
||||||
|
bool show_plus_sign { false };
|
||||||
|
int width { -1 };
|
||||||
|
int percision { -1 };
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
static void integer_to_string(char* buffer, T value, int base, bool upper, const format_options_t options)
|
||||||
|
{
|
||||||
|
int width = 1;
|
||||||
|
bool zero_padded = options.zero_padded;
|
||||||
|
if (options.percision != -1)
|
||||||
|
{
|
||||||
|
width = options.percision;
|
||||||
|
zero_padded = false;
|
||||||
|
}
|
||||||
|
else if (options.width != -1)
|
||||||
|
width = options.width;
|
||||||
|
|
||||||
|
auto digit_char = [](int digit, bool upper)
|
||||||
|
{
|
||||||
|
if (digit < 10)
|
||||||
|
return '0' + digit;
|
||||||
|
return (upper ? 'A' : 'a') + (digit - 10);
|
||||||
|
};
|
||||||
|
|
||||||
|
bool sign = false;
|
||||||
|
int offset = 0;
|
||||||
|
if (value < 0)
|
||||||
|
{
|
||||||
|
sign = true;
|
||||||
|
buffer[offset++] = digit_char(-(value % base), upper);
|
||||||
|
value = -(value / base);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (value)
|
||||||
|
{
|
||||||
|
buffer[offset++] = digit_char(value % base, upper);
|
||||||
|
value /= base;
|
||||||
|
}
|
||||||
|
|
||||||
|
int prefix_length = 0;
|
||||||
|
|
||||||
|
if (sign || options.show_plus_sign || options.show_plus_sign_as_space)
|
||||||
|
prefix_length++;
|
||||||
|
if (base == 8 && options.alternate_form)
|
||||||
|
prefix_length++;
|
||||||
|
if (base == 16 && options.alternate_form)
|
||||||
|
prefix_length += 2;
|
||||||
|
|
||||||
|
while (offset < width - prefix_length)
|
||||||
|
buffer[offset++] = (zero_padded ? '0' : ' ');
|
||||||
|
|
||||||
|
if (sign)
|
||||||
|
buffer[offset++] = '-';
|
||||||
|
else if (options.show_plus_sign)
|
||||||
|
buffer[offset++] = '+';
|
||||||
|
else if (options.show_plus_sign_as_space)
|
||||||
|
buffer[offset++] = ' ';
|
||||||
|
|
||||||
|
if (base == 8 && options.alternate_form)
|
||||||
|
buffer[offset++] = '0';
|
||||||
|
if (base == 16 && options.alternate_form)
|
||||||
|
{
|
||||||
|
buffer[offset++] = 'x';
|
||||||
|
buffer[offset++] = '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < offset / 2; i++)
|
||||||
|
{
|
||||||
|
char temp = buffer[i];
|
||||||
|
buffer[i] = buffer[offset - i - 1];
|
||||||
|
buffer[offset - i - 1] = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer[offset++] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
static void floating_point_to_exponent_string(char* buffer, T value, bool upper, const format_options_t options)
|
||||||
|
{
|
||||||
|
int percision = 6;
|
||||||
|
if (options.percision != -1)
|
||||||
|
percision = options.percision;
|
||||||
|
|
||||||
|
strcpy(buffer, "??e??");
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
static void floating_point_to_string(char* buffer, T value, bool upper, const format_options_t options)
|
||||||
|
{
|
||||||
|
int percision = 6;
|
||||||
|
if (options.percision != -1)
|
||||||
|
percision = options.percision;
|
||||||
|
|
||||||
|
strcpy(buffer, "??.??");
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" int printf_impl(const char* format, va_list arguments, int (*putc_fun)(int, void*), void* data)
|
||||||
|
{
|
||||||
|
int written = 0;
|
||||||
|
while (*format)
|
||||||
|
{
|
||||||
|
if (*format == '%')
|
||||||
|
{
|
||||||
|
format_options_t options;
|
||||||
|
|
||||||
|
// PARSE FLAGS
|
||||||
|
bool flags_done = false;
|
||||||
|
while (!flags_done)
|
||||||
|
{
|
||||||
|
format++;
|
||||||
|
switch (*format)
|
||||||
|
{
|
||||||
|
case '#': options.alternate_form = true; break;
|
||||||
|
case '0': options.zero_padded = true; break;
|
||||||
|
case '-': options.left_justified = true; break;
|
||||||
|
case ' ': options.show_plus_sign_as_space = true; break;
|
||||||
|
case '+': options.show_plus_sign = true; break;
|
||||||
|
default:
|
||||||
|
flags_done = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (options.zero_padded && options.left_justified)
|
||||||
|
options.zero_padded = false;
|
||||||
|
|
||||||
|
// PARSE FIELD WIDTH
|
||||||
|
if (*format == '*')
|
||||||
|
{
|
||||||
|
options.width = va_arg(arguments, int);
|
||||||
|
format++;
|
||||||
|
}
|
||||||
|
else if (isdigit(*format) && *format != '0')
|
||||||
|
{
|
||||||
|
int width = 0;
|
||||||
|
while (isdigit(*format))
|
||||||
|
{
|
||||||
|
width *= 10;
|
||||||
|
width += *format - '0';
|
||||||
|
format++;
|
||||||
|
}
|
||||||
|
options.width = width;
|
||||||
|
}
|
||||||
|
|
||||||
|
// PARSE PERCISION
|
||||||
|
if (*format == '.')
|
||||||
|
{
|
||||||
|
format++;
|
||||||
|
int percision = 0;
|
||||||
|
if (isdigit(*format))
|
||||||
|
{
|
||||||
|
while (isdigit(*format))
|
||||||
|
{
|
||||||
|
percision *= 10;
|
||||||
|
percision += *format - '0';
|
||||||
|
format++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (*format == '*')
|
||||||
|
{
|
||||||
|
percision = va_arg(arguments, int);
|
||||||
|
}
|
||||||
|
options.percision = percision;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Lenght modifier
|
||||||
|
|
||||||
|
static char conversion[100];
|
||||||
|
|
||||||
|
const char* string = nullptr;
|
||||||
|
switch (*format)
|
||||||
|
{
|
||||||
|
case 'd':
|
||||||
|
case 'i':
|
||||||
|
{
|
||||||
|
int value = va_arg(arguments, int);
|
||||||
|
integer_to_string<int>(conversion, value, 10, false, options);
|
||||||
|
string = conversion;
|
||||||
|
format++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'o':
|
||||||
|
{
|
||||||
|
unsigned int value = va_arg(arguments, unsigned int);
|
||||||
|
integer_to_string<unsigned int>(conversion, value, 8, false, options);
|
||||||
|
string = conversion;
|
||||||
|
format++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'u':
|
||||||
|
{
|
||||||
|
unsigned int value = va_arg(arguments, unsigned int);
|
||||||
|
integer_to_string<unsigned int>(conversion, value, 10, false, options);
|
||||||
|
string = conversion;
|
||||||
|
format++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'x':
|
||||||
|
case 'X':
|
||||||
|
{
|
||||||
|
unsigned int value = va_arg(arguments, unsigned int);
|
||||||
|
integer_to_string<unsigned int>(conversion, value, 16, *format == 'X', options);
|
||||||
|
string = conversion;
|
||||||
|
format++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'e':
|
||||||
|
case 'E':
|
||||||
|
{
|
||||||
|
double value = va_arg(arguments, double);
|
||||||
|
floating_point_to_exponent_string<double>(conversion, value, *format == 'E', options);
|
||||||
|
string = conversion;
|
||||||
|
format++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'f':
|
||||||
|
case 'F':
|
||||||
|
{
|
||||||
|
double value = va_arg(arguments, double);
|
||||||
|
floating_point_to_string<double>(conversion, value, *format == 'F', options);
|
||||||
|
string = conversion;
|
||||||
|
format++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'g':
|
||||||
|
case 'G':
|
||||||
|
// TODO
|
||||||
|
break;
|
||||||
|
case 'a':
|
||||||
|
case 'A':
|
||||||
|
// TODO
|
||||||
|
break;
|
||||||
|
case 'c':
|
||||||
|
{
|
||||||
|
conversion[0] = va_arg(arguments, int);
|
||||||
|
conversion[1] = '\0';
|
||||||
|
string = conversion;
|
||||||
|
format++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 's':
|
||||||
|
{
|
||||||
|
string = va_arg(arguments, const char*);
|
||||||
|
format++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'C':
|
||||||
|
// TODO (depricated)
|
||||||
|
break;
|
||||||
|
case 'S':
|
||||||
|
// TODO (depricated)
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
{
|
||||||
|
void* ptr = va_arg(arguments, void*);
|
||||||
|
options.alternate_form = true;
|
||||||
|
integer_to_string<uintptr_t>(conversion, (uintptr_t)ptr, 16, false, options);
|
||||||
|
string = conversion;
|
||||||
|
format++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'n':
|
||||||
|
{
|
||||||
|
int* target = va_arg(arguments, int*);
|
||||||
|
*target = written;
|
||||||
|
format++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'm':
|
||||||
|
{
|
||||||
|
// NOTE: this is a glibc extension
|
||||||
|
if (options.alternate_form)
|
||||||
|
string = strerror(errno); // TODO: string = strerrorname_np(errno);
|
||||||
|
else
|
||||||
|
string = strerror(errno);
|
||||||
|
format++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case '%':
|
||||||
|
{
|
||||||
|
conversion[0] = '%';
|
||||||
|
conversion[1] = '\0';
|
||||||
|
string = conversion;
|
||||||
|
format++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string)
|
||||||
|
{
|
||||||
|
int len = strlen(string);
|
||||||
|
|
||||||
|
if (options.width == -1)
|
||||||
|
options.width = 0;
|
||||||
|
|
||||||
|
if (!options.left_justified)
|
||||||
|
for (int i = len; i < options.width; i++)
|
||||||
|
BAN_PRINTF_PUTC(' ');
|
||||||
|
|
||||||
|
while (*string)
|
||||||
|
{
|
||||||
|
BAN_PRINTF_PUTC(*string);
|
||||||
|
string++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.left_justified)
|
||||||
|
for (int i = len; i < options.width; i++)
|
||||||
|
BAN_PRINTF_PUTC(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BAN_PRINTF_PUTC(*format);
|
||||||
|
format++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return written;
|
||||||
|
}
|
||||||
|
|
100
libc/stdio.cpp
100
libc/stdio.cpp
|
@ -1,5 +1,6 @@
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
#include <printf_impl.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
@ -426,11 +427,23 @@ void setbuf(FILE*, char*);
|
||||||
// TODO
|
// TODO
|
||||||
int setvbuf(FILE*, char*, int, size_t);
|
int setvbuf(FILE*, char*, int, size_t);
|
||||||
|
|
||||||
// TODO
|
int snprintf(char* buffer, size_t max_size, const char* format, ...)
|
||||||
int snprintf(char*, size_t, const char*, ...);
|
{
|
||||||
|
va_list arguments;
|
||||||
|
va_start(arguments, format);
|
||||||
|
int ret = vsnprintf(buffer, max_size, format, arguments);
|
||||||
|
va_end(arguments);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO
|
int sprintf(char* buffer, const char* format, ...)
|
||||||
int sprintf(char*, const char*, ...);
|
{
|
||||||
|
va_list arguments;
|
||||||
|
va_start(arguments, format);
|
||||||
|
int ret = vsprintf(buffer, format, arguments);
|
||||||
|
va_end(arguments);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
int sscanf(const char*, const char*, ...);
|
int sscanf(const char*, const char*, ...);
|
||||||
|
@ -449,43 +462,7 @@ int ungetc(int, FILE*);
|
||||||
|
|
||||||
int vfprintf(FILE* file, const char* format, va_list arguments)
|
int vfprintf(FILE* file, const char* format, va_list arguments)
|
||||||
{
|
{
|
||||||
int written = 0;
|
return printf_impl(format, arguments, [](int c, void* file) { return fputc(c, (FILE*)file); }, file);
|
||||||
while (*format)
|
|
||||||
{
|
|
||||||
if (*format == '%')
|
|
||||||
{
|
|
||||||
format++;
|
|
||||||
switch (*format)
|
|
||||||
{
|
|
||||||
case '%':
|
|
||||||
if (fputc('%', file) == EOF)
|
|
||||||
return -1;
|
|
||||||
written++;
|
|
||||||
format++;
|
|
||||||
break;
|
|
||||||
case 's':
|
|
||||||
{
|
|
||||||
const char* string = va_arg(arguments, const char*);
|
|
||||||
if (fputs(string, file) == EOF)
|
|
||||||
return -1;
|
|
||||||
written += strlen(string);
|
|
||||||
format++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (fputc(*format, file) == EOF)
|
|
||||||
return -1;
|
|
||||||
written++;
|
|
||||||
format++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return written;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
|
@ -500,10 +477,47 @@ int vprintf(const char* format, va_list arguments)
|
||||||
int vscanf(const char*, va_list);
|
int vscanf(const char*, va_list);
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
int vsnprintf(char*, size_t, const char*, va_list);
|
int vsnprintf(char* buffer, size_t max_size, const char* format, va_list arguments)
|
||||||
|
{
|
||||||
|
if (buffer == nullptr)
|
||||||
|
return printf_impl(format, arguments, [](int, void*) { return 0; }, nullptr);
|
||||||
|
|
||||||
|
struct print_info
|
||||||
|
{
|
||||||
|
char* buffer;
|
||||||
|
size_t remaining;
|
||||||
|
};
|
||||||
|
print_info info { buffer, max_size };
|
||||||
|
|
||||||
|
return printf_impl(format, arguments,
|
||||||
|
[](int c, void* _info)
|
||||||
|
{
|
||||||
|
print_info* info = (print_info*)_info;
|
||||||
|
if (info->remaining)
|
||||||
|
{
|
||||||
|
*info->buffer = (info->remaining == 1 ? '\0' : c);
|
||||||
|
info->buffer++;
|
||||||
|
info->remaining--;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}, &info
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
int vsprintf(char*, const char*, va_list);
|
int vsprintf(char* buffer, const char* format, va_list arguments)
|
||||||
|
{
|
||||||
|
if (buffer == nullptr)
|
||||||
|
return printf_impl(format, arguments, [](int, void*) { return 0; }, nullptr);
|
||||||
|
|
||||||
|
return printf_impl(format, arguments,
|
||||||
|
[](int c, void* _buffer)
|
||||||
|
{
|
||||||
|
*(*(char**)_buffer)++ = c;
|
||||||
|
return 0;
|
||||||
|
}, &buffer
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
int vsscanf(const char*, const char*, va_list);
|
int vsscanf(const char*, const char*, va_list);
|
||||||
|
|
Loading…
Reference in New Issue