From 49fe3d0d4f69e1b784a35226e3a8d2931d116965 Mon Sep 17 00:00:00 2001 From: Bananymous Date: Wed, 10 May 2023 02:00:28 +0300 Subject: [PATCH] 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 --- libc/CMakeLists.txt | 1 + libc/include/printf_impl.h | 11 ++ libc/printf_impl.cpp | 341 +++++++++++++++++++++++++++++++++++++ libc/stdio.cpp | 100 ++++++----- 4 files changed, 410 insertions(+), 43 deletions(-) create mode 100644 libc/include/printf_impl.h create mode 100644 libc/printf_impl.cpp diff --git a/libc/CMakeLists.txt b/libc/CMakeLists.txt index 2d23f273..a74abdbd 100644 --- a/libc/CMakeLists.txt +++ b/libc/CMakeLists.txt @@ -5,6 +5,7 @@ project(libc CXX) set(LIBC_SOURCES ctype.cpp fcntl.cpp + printf_impl.cpp stdio.cpp stdlib.cpp string.cpp diff --git a/libc/include/printf_impl.h b/libc/include/printf_impl.h new file mode 100644 index 00000000..feaeea03 --- /dev/null +++ b/libc/include/printf_impl.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include +#include + +__BEGIN_DECLS + +int printf_impl(const char* format, va_list arguments, int (*putc_fun)(int, void*), void* data); + +__END_DECLS diff --git a/libc/printf_impl.cpp b/libc/printf_impl.cpp new file mode 100644 index 00000000..a47891d0 --- /dev/null +++ b/libc/printf_impl.cpp @@ -0,0 +1,341 @@ +#include +#include +#include +#include + +#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 +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 +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 +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(conversion, value, 10, false, options); + string = conversion; + format++; + break; + } + case 'o': + { + unsigned int value = va_arg(arguments, unsigned int); + integer_to_string(conversion, value, 8, false, options); + string = conversion; + format++; + break; + } + case 'u': + { + unsigned int value = va_arg(arguments, unsigned int); + integer_to_string(conversion, value, 10, false, options); + string = conversion; + format++; + break; + } + case 'x': + case 'X': + { + unsigned int value = va_arg(arguments, unsigned int); + integer_to_string(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(conversion, value, *format == 'E', options); + string = conversion; + format++; + break; + } + case 'f': + case 'F': + { + double value = va_arg(arguments, double); + floating_point_to_string(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(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; +} + diff --git a/libc/stdio.cpp b/libc/stdio.cpp index 457714ad..0cc00357 100644 --- a/libc/stdio.cpp +++ b/libc/stdio.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -426,11 +427,23 @@ void setbuf(FILE*, char*); // TODO int setvbuf(FILE*, char*, int, size_t); -// TODO -int snprintf(char*, size_t, const char*, ...); +int snprintf(char* buffer, size_t max_size, const char* format, ...) +{ + va_list arguments; + va_start(arguments, format); + int ret = vsnprintf(buffer, max_size, format, arguments); + va_end(arguments); + return ret; +} -// TODO -int sprintf(char*, const char*, ...); +int sprintf(char* buffer, const char* format, ...) +{ + va_list arguments; + va_start(arguments, format); + int ret = vsprintf(buffer, format, arguments); + va_end(arguments); + return ret; +} // TODO 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 written = 0; - 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; + return printf_impl(format, arguments, [](int c, void* file) { return fputc(c, (FILE*)file); }, file); } // TODO @@ -500,10 +477,47 @@ int vprintf(const char* format, va_list arguments) int vscanf(const char*, va_list); // 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 -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 int vsscanf(const char*, const char*, va_list);