From 011ea58176626f17d27df97fec375145c9de083d Mon Sep 17 00:00:00 2001 From: Jonas 'Sortie' Termansen Date: Mon, 21 Oct 2013 02:00:59 +0200 Subject: [PATCH] Rewrite vprintf_callback(3). --- libc/Makefile | 2 +- libc/stdio/format.cpp | 527 -------------------------------- libc/stdio/vprintf_callback.cpp | 408 +++++++++++++++++++++++++ 3 files changed, 409 insertions(+), 528 deletions(-) delete mode 100644 libc/stdio/format.cpp create mode 100644 libc/stdio/vprintf_callback.cpp diff --git a/libc/Makefile b/libc/Makefile index 9e7c5b4e..6d638172 100644 --- a/libc/Makefile +++ b/libc/Makefile @@ -56,7 +56,6 @@ stdio/flbf.o \ stdio/flockfile.o \ stdio/flushlbf.o \ stdio/fnewfile.o \ -stdio/format.o \ stdio/fpending.o \ stdio/fpurge.o \ stdio/fputc.o \ @@ -91,6 +90,7 @@ stdio/sscanf.o \ stdio/ungetc.o \ stdio/vdprintf.o \ stdio/vfscanf.o \ +stdio/vprintf_callback.o \ stdio/vsnprintf.o \ stdio/vsprintf.o \ stdio/vsscanf.o \ diff --git a/libc/stdio/format.cpp b/libc/stdio/format.cpp deleted file mode 100644 index 5b04ce28..00000000 --- a/libc/stdio/format.cpp +++ /dev/null @@ -1,527 +0,0 @@ -/******************************************************************************* - - Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. - - This file is part of the Sortix C Library. - - The Sortix C Library is free software: you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation, either version 3 of the License, or (at your - option) any later version. - - The Sortix C Library is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with the Sortix C Library. If not, see . - - stdio/format.cpp - Provides printf formatting functions that uses callbacks. - -*******************************************************************************/ - -// Number of bugs seemingly unrelated bugs that have been traced to here: -// Countless + 2 - -#include -#include -#include -#include - -namespace String { - -static int ConvertInt32(int32_t num, char* dest, char possign) -{ - int result = 0; - int32_t copy = num; - - if ( num < 0 ) - { - *dest++ = '-'; - result++; - - int offset = 0; - while ( copy < -9 ) { copy /= 10; offset++; } - result += offset; - while ( offset >= 0 ) - { - dest[offset] = '0' - num % 10; num /= 10; offset--; - } - } - else - { - if ( possign ) - *dest++ = possign, - result++; - - int offset = 0; - while ( copy > 9 ) { copy /= 10; offset++; } - result += offset; - while ( offset >= 0 ) - { - dest[offset] = '0' + num % 10; num /= 10; offset--; - } - } - - return result + 1; -} - -static int ConvertInt64(int64_t num, char* dest, char possign) -{ - int result = 0; - int64_t copy = num; - - if ( num < 0 ) - { - *dest++ = '-'; - result++; - - int offset = 0; - while ( copy < -9 ) { copy /= 10; offset++; } - result += offset; - while ( offset >= 0 ) - { - dest[offset] = '0' - num % 10; num /= 10; offset--; - } - } - else - { - if ( possign ) - *dest++ = possign, - result++; - - int offset = 0; - while ( copy > 9 ) { copy /= 10; offset++; } - result += offset; - while ( offset >= 0 ) - { - dest[offset] = '0' + num % 10; num /= 10; offset--; - } - } - - return result + 1; -} - -static int ConvertUInt32(uint32_t num, char* dest) -{ - int result = 0; - uint32_t copy = num; - int offset = 0; - while ( copy > 9 ) { copy /= 10; offset++; } - result += offset; - while ( offset >= 0 ) - { - dest[offset] = '0' + num % 10; num /= 10; offset--; - } - - return result + 1; -} - -static int ConvertUInt64(uint64_t num, char* dest) -{ - int result = 0; - uint64_t copy = num; - int offset = 0; - while ( copy > 9 ) { copy /= 10; offset++; } - result += offset; - while ( offset >= 0 ) - { - dest[offset] = '0' + num % 10; num /= 10; offset--; - } - - return result + 1; -} - -static int ConvertUInt32Octal(uint32_t num, char* dest) -{ - int result = 0; - uint32_t copy = num; - int offset = 0; - while ( copy > 7 ) { copy /= 8; offset++; } - result += offset; - while ( offset >= 0 ) - { - dest[offset] = '0' + num % 8; num /= 8; offset--; - } - - return result + 1; -} - -static int ConvertUInt64Octal(uint64_t num, char* dest) -{ - int result = 0; - uint64_t copy = num; - int offset = 0; - while ( copy > 7 ) { copy /= 8; offset++; } - result += offset; - while ( offset >= 0 ) - { - dest[offset] = '0' + num % 8; num /= 8; offset--; - } - - return result + 1; -} - -static int ConvertUInt32Hex(uint32_t num, char* dest) -{ - char chars[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - 'A', 'B', 'C', 'D', 'E', 'F' }; - int result = 0; - uint32_t copy = num; - int offset = 0; - while ( copy > 15 ) { copy /= 16; offset++; } - result += offset; - while ( offset >= 0 ) - { - dest[offset] = chars[num % 16]; num /= 16; offset--; - } - - return result + 1; -} - -static int ConvertUInt64Hex(uint64_t num, char* dest) -{ - char chars[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - 'A', 'B', 'C', 'D', 'E', 'F' }; - int result = 0; - uint64_t copy = num; - int offset = 0; - while ( copy > 15 ) { copy /= 16; offset++; } - result += offset; - while ( offset >= 0 ) - { - dest[offset] = chars[num % 16]; num /= 16; offset--; - } - - return result + 1; -} - -} // namespace String - -#define READY_SIZE 128 - -#define READY_FLUSH() \ - ready[readylen] = '\0'; \ - if ( 0 < readylen && callback && callback(user, ready, readylen) != readylen ) { return SIZE_MAX; } \ - written += readylen; readylen = 0; - -#define REPEAT_BLANKS(num) \ - for ( unsigned int i = 0; i < (num); i++ ) \ - { \ - if ( readylen == READY_SIZE ) { READY_FLUSH(); } \ - ready[readylen++ + i - i] = blank_char; \ - } - -extern "C" size_t vprintf_callback(size_t (*callback)(void*, const char*, size_t), - void* user, - const char* restrict format, - va_list parameters) -{ - size_t written = 0; - size_t readylen = 0; - char ready[READY_SIZE + 1]; - - while ( *format != '\0' ) - { - char c = *format++; - - if ( c != '%' ) - { - print_c: - if ( READY_SIZE <= readylen ) { READY_FLUSH(); } - ready[readylen++] = c; - continue; - } - - if ( *format == '%' ) - { - c = *format++; - goto print_c; - } - - const char* format_begun_at = format; - if ( false ) - { - unsupported_conversion: - format = format_begun_at; - c = '%'; - goto print_c; - } - - const unsigned UNSIGNED = 0; - const unsigned INTEGER = (1<<0); - const unsigned BIT64 = (1<<1); - const unsigned HEX = (1<<2); - const unsigned OCTAL = (1<<3); - const unsigned STRING = 16; - const unsigned CHARACTER = 17; - #if defined(__x86_64__) - const unsigned WORDWIDTH = BIT64; - #else - const unsigned WORDWIDTH = 0; - #endif - - // TODO: Support signed datatypes! - - unsigned type = 0; - - bool prepend_chars = false; - bool append_chars = false; - bool space_pad = false; - bool zero_pad = false; - bool alternate = false; - bool possign_space = false; - bool possign_plus = false; - char blank_char = ' '; - unsigned int field_width = 0; - - bool scanning = true; - while ( scanning ) - { - switch ( char c = *(format++) ) - { - case ' ': - possign_space = true; - break; - case '+': - possign_plus = true; - break; - case '#': - alternate = true; - break; - case '-': - if ( prepend_chars || append_chars ) - goto unsupported_conversion; - append_chars = space_pad = true; - prepend_chars = zero_pad = false; - blank_char = ' '; - break; - case '*': - if ( !(prepend_chars || append_chars) ) - prepend_chars = true; - field_width = (unsigned int) va_arg(parameters, int); - break; - case '0': - if ( !(prepend_chars || append_chars) || (!field_width && space_pad) ) - { - prepend_chars = zero_pad = true; - append_chars = space_pad = false; - blank_char = '0'; - break; - } - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - if ( !(prepend_chars || append_chars) ) - prepend_chars = true; - field_width = field_width * 10 + c - '0'; - break; - case 'p': - type = WORDWIDTH | HEX; - scanning = false; - break; - case 't': - type |= INTEGER; - case 'z': - case 'l': - if ( type & WORDWIDTH ) { type |= BIT64; } - type |= WORDWIDTH; - break; - case 'j': - type |= BIT64; - break; - case 'u': - type |= UNSIGNED; - scanning = false; - break; - case 'd': - case 'i': - type |= INTEGER; - scanning = false; - break; - case 'o': - type |= OCTAL; - scanning = false; - break; - case 'x': - case 'X': - type |= HEX; - scanning = false; - break; - case 's': - type = STRING; - scanning = false; - break; - case 'c': - type = CHARACTER; - scanning = false; - break; - default: - goto unsupported_conversion; - } - } - - char possign = '\0'; - if ( possign_plus ) - possign = '+'; - else if ( possign_space ) - possign = ' '; - - switch ( type ) - { - case INTEGER: - { - if ( READY_SIZE - readylen < 10 ) { READY_FLUSH(); } - int32_t num = va_arg(parameters, int32_t); - size_t chars = String::ConvertInt32(num, ready + readylen, possign); - if ( prepend_chars && chars < field_width ) { REPEAT_BLANKS(field_width - chars); } - if ( READY_SIZE - readylen < 10 ) { READY_FLUSH(); } - String::ConvertInt32(num, ready + readylen, possign); - readylen += chars; - if ( append_chars && chars < field_width ) { REPEAT_BLANKS(field_width - chars); } - break; - } - case UNSIGNED: - { - if ( READY_SIZE - readylen < 10 ) { READY_FLUSH(); } - uint32_t num = va_arg(parameters, uint32_t); - size_t chars = String::ConvertUInt32(num, ready + readylen); - if ( prepend_chars && chars < field_width ) { REPEAT_BLANKS(field_width - chars); } - if ( READY_SIZE - readylen < 10 ) { READY_FLUSH(); } - String::ConvertUInt32(num, ready + readylen); - readylen += chars; - if ( append_chars && chars < field_width ) { REPEAT_BLANKS(field_width - chars); } - break; - } - case INTEGER | BIT64: - { - if ( READY_SIZE - readylen < 20 ) { READY_FLUSH(); } - int64_t num = va_arg(parameters, int64_t); - size_t chars = String::ConvertInt64(num, ready + readylen, possign); - if ( prepend_chars && chars < field_width ) { REPEAT_BLANKS(field_width - chars); } - if ( READY_SIZE - readylen < 20 ) { READY_FLUSH(); } - String::ConvertInt64(num, ready + readylen, possign); - readylen += chars; - if ( append_chars && chars < field_width ) { REPEAT_BLANKS(field_width - chars); } - break; - } - case UNSIGNED | BIT64: - { - if ( READY_SIZE - readylen < 20 ) { READY_FLUSH(); } - uint64_t num = va_arg(parameters, uint64_t); - size_t chars = String::ConvertUInt64(num, ready + readylen); - if ( prepend_chars && chars < field_width ) { REPEAT_BLANKS(field_width - chars); } - if ( READY_SIZE - readylen < 20 ) { READY_FLUSH(); } - String::ConvertUInt64(num, ready + readylen); - readylen += chars; - if ( append_chars && chars < field_width ) { REPEAT_BLANKS(field_width - chars); } - break; - } - case INTEGER | HEX: - case UNSIGNED | HEX: - { - if ( READY_SIZE - readylen < 8 ) { READY_FLUSH(); } - uint32_t num = va_arg(parameters, uint32_t); - size_t chars = String::ConvertUInt32Hex(num, ready + readylen); - size_t real_chars = chars; - if ( num && alternate ) chars += 2; - if ( prepend_chars && chars < field_width ) { REPEAT_BLANKS(field_width - chars); } - if ( READY_SIZE - readylen < 8 + 2 ) { READY_FLUSH(); } - if ( alternate && num ) - ready[readylen++] = '0', - ready[readylen++] = 'x'; - String::ConvertUInt32Hex(num, ready + readylen); - readylen += real_chars; - if ( append_chars && chars < field_width ) { REPEAT_BLANKS(field_width - chars); } - break; - } - case INTEGER | BIT64 | HEX: - case UNSIGNED | BIT64 | HEX: - { - if ( READY_SIZE - readylen < 16 ) { READY_FLUSH(); } - uint64_t num = va_arg(parameters, uint64_t); - size_t chars = String::ConvertUInt64Hex(num, ready + readylen); - size_t real_chars = chars; - if ( num && alternate ) chars += 2; - if ( prepend_chars && chars < field_width ) { REPEAT_BLANKS(field_width - chars); } - if ( READY_SIZE - readylen < 16 + 2 ) { READY_FLUSH(); } - if ( alternate && num ) - ready[readylen++] = '0', - ready[readylen++] = 'x'; - String::ConvertUInt64Hex(num, ready + readylen); - readylen += real_chars; - if ( append_chars && chars < field_width ) { REPEAT_BLANKS(field_width - chars); } - break; - } - case INTEGER | OCTAL: - case UNSIGNED | OCTAL: - { - if ( READY_SIZE - readylen < 20 ) { READY_FLUSH(); } - uint32_t num = va_arg(parameters, uint32_t); - size_t chars = String::ConvertUInt32Octal(num, ready + readylen); - size_t real_chars = chars; - if ( num && alternate ) chars += 1; - if ( prepend_chars && chars < field_width ) { REPEAT_BLANKS(field_width - chars); } - if ( READY_SIZE - readylen < 20 + 1 ) { READY_FLUSH(); } - if ( alternate && num ) - ready[readylen++] = '0'; - String::ConvertUInt32Octal(num, ready + readylen); - readylen += real_chars; - if ( append_chars && chars < field_width ) { REPEAT_BLANKS(field_width - chars); } - break; - } - case INTEGER | BIT64 | OCTAL: - case UNSIGNED | BIT64 | OCTAL: - { - if ( READY_SIZE - readylen < 40 ) { READY_FLUSH(); } - uint64_t num = va_arg(parameters, uint64_t); - size_t chars = String::ConvertUInt64Octal(num, ready + readylen); - size_t real_chars = chars; - if ( num && alternate ) chars += 1; - if ( prepend_chars && chars < field_width ) { REPEAT_BLANKS(field_width - chars); } - if ( READY_SIZE - readylen < 40 + 1 ) { READY_FLUSH(); } - if ( alternate && num ) - ready[readylen++] = '0'; - String::ConvertUInt64Octal(num, ready + readylen); - readylen += real_chars; - if ( append_chars && chars < field_width ) { REPEAT_BLANKS(field_width - chars); } - break; - } - case STRING: - { - const char* str = va_arg(parameters, const char*); - size_t len = strlen(str); - size_t chars = len; - if ( prepend_chars && chars < field_width ) { REPEAT_BLANKS(field_width - chars); } - READY_FLUSH(); - if ( callback && callback(user, str, len) != len ) { return SIZE_MAX; } - if ( append_chars && chars < field_width ) { REPEAT_BLANKS(field_width - chars); } - written += len; - break; - } - case CHARACTER: - { - int c = va_arg(parameters, int); - size_t len = 1; - size_t chars = len; - if ( prepend_chars && chars < field_width ) { REPEAT_BLANKS(field_width - chars); } - if ( READY_SIZE <= readylen ) { READY_FLUSH(); } - ready[readylen++] = c; - if ( append_chars && chars < field_width ) { REPEAT_BLANKS(field_width - chars); } - break; - } - } - } - - READY_FLUSH(); - - return written; -} diff --git a/libc/stdio/vprintf_callback.cpp b/libc/stdio/vprintf_callback.cpp new file mode 100644 index 00000000..0e5c12c6 --- /dev/null +++ b/libc/stdio/vprintf_callback.cpp @@ -0,0 +1,408 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012, 2013. + + This file is part of the Sortix C Library. + + The Sortix C Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + The Sortix C Library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with the Sortix C Library. If not, see . + + stdio/vprintf_callback.cpp + Provides printf formatting functions that uses callbacks. + +*******************************************************************************/ + +// Number of bugs seemingly unrelated bugs that have been traced to here: +// Countless + 2 + +#include +#include +#include +#include +#include +#include +#include + +static size_t convert_integer(char* destination, uintmax_t value, + uintmax_t base, const char* digits) +{ + size_t result = 1; + uintmax_t copy = value; + while ( base <= copy ) + copy /= base, result++; + for ( size_t i = result; i != 0; i-- ) + destination[i-1] = digits[value % base], + value /= base; + return result; +} + +static size_t noop_callback(void*, const char*, size_t amount) +{ + return amount; +} + +extern "C" +size_t vprintf_callback(size_t (*callback)(void*, const char*, size_t), + void* user, + const char* restrict format, + va_list parameters) +{ + if ( !callback ) + callback = noop_callback; + + size_t written = 0; + bool rejected_bad_specifier = false; + + while ( *format != '\0' ) + { + if ( *format != '%' ) + { + print_c: + size_t amount = 1; + while ( format[amount] && format[amount] != '%' ) + amount++; + if ( callback(user, format, amount) != amount ) + return SIZE_MAX; + format += amount; + continue; + } + + const char* format_begun_at = format; + + if ( *(++format) == '%' ) + goto print_c; + + if ( rejected_bad_specifier ) + { + incomprehensible_conversion: + rejected_bad_specifier = true; + unsupported_conversion: + format = format_begun_at; + goto print_c; + } + + bool alternate = false; + bool zero_pad = false; + bool field_width_is_negative = false; + bool prepend_blank_if_positive = false; + bool prepend_plus_if_positive = false; + bool group_thousands = false; + bool alternate_output_digits = false; + + (void) group_thousands; + (void) alternate_output_digits; + + do switch ( *format++ ) + { + case '#': alternate = true; continue; + case '0': zero_pad = true; continue; + case '-': field_width_is_negative = true; continue; + case ' ': prepend_blank_if_positive = true; continue; + case '+': prepend_plus_if_positive = true; continue; + case '\'': group_thousands = true; continue; + case 'I': alternate_output_digits = true; continue; + default: format--; break; + } while ( false ); + + int field_width = 0; + if ( *format == '*' && (format++, true) ) + field_width = va_arg(parameters, int); + else while ( '0' <= *format && *format <= '9' ) + field_width = 10 * field_width + *format++ - '0'; + if ( field_width_is_negative ) + field_width = -field_width; + size_t abs_field_width = (size_t) abs(field_width); + + size_t precision = SIZE_MAX; + if ( *format == '.' && (format++, true) ) + { + precision = 0; + if ( *format == '*' && (format++, true) ) + { + int int_precision = va_arg(parameters, int); + precision = 0 <= int_precision ? (size_t) int_precision : 0; + } + else + { + if ( *format == '-' && (format++, true) ) + while ( '0' <= *format && *format <= '9' ) + format++; + else + while ( '0' <= *format && *format <= '9' ) + precision = 10 * precision + *format++ - '0'; + } + } + + enum length + { + LENGTH_SHORT_SHORT, + LENGTH_SHORT, + LENGTH_DEFAULT, + LENGTH_LONG, + LENGTH_LONG_LONG, + LENGTH_LONG_DOUBLE, + LENGTH_INTMAX_T, + LENGTH_SIZE_T, + LENGTH_PTRDIFF_T, + }; + + struct length_modifer + { + const char* name; + enum length length; + }; + + struct length_modifer length_modifiers[] = + { + { "hh", LENGTH_SHORT_SHORT }, + { "h", LENGTH_SHORT }, + { "", LENGTH_DEFAULT }, + { "l", LENGTH_LONG }, + { "ll", LENGTH_LONG_LONG }, + { "L", LENGTH_LONG_DOUBLE }, + { "j", LENGTH_INTMAX_T }, + { "z", LENGTH_SIZE_T }, + { "t", LENGTH_PTRDIFF_T }, + }; + + enum length length = LENGTH_DEFAULT; + size_t length_length = 0; + for ( size_t i = 0; + i < sizeof(length_modifiers) / sizeof(length_modifiers[0]); + i++ ) + { + size_t name_length = strlen(length_modifiers[i].name); + if ( name_length < length_length ) + continue; + if ( strncmp(format, length_modifiers[i].name, name_length) != 0 ) + continue; + length = length_modifiers[i].length; + length_length = name_length; + } + + format += length_length; + + if ( *format == 'd' || *format == 'i' || *format == 'o' || + *format == 'u' || *format == 'x' || *format == 'X' || + *format == 'p' ) + { + char conversion = *format++; + + bool negative_value = false; + uintmax_t value; + if ( conversion == 'p' ) + { + value = (uintmax_t) va_arg(parameters, void*); + conversion = 'x'; + alternate = !alternate; + } + else if ( conversion == 'i' || conversion == 'd' ) + { + intmax_t signed_value; + if ( length == LENGTH_SHORT_SHORT ) + signed_value = va_arg(parameters, int); + else if ( length == LENGTH_SHORT ) + signed_value = va_arg(parameters, int); + else if ( length == LENGTH_DEFAULT ) + signed_value = va_arg(parameters, int); + else if ( length == LENGTH_LONG ) + signed_value = va_arg(parameters, long); + else if ( length == LENGTH_LONG_LONG ) + signed_value = va_arg(parameters, long long); + else if ( length == LENGTH_INTMAX_T ) + signed_value = va_arg(parameters, intmax_t); + else if ( length == LENGTH_SIZE_T ) + signed_value = va_arg(parameters, ssize_t); + else if ( length == LENGTH_PTRDIFF_T ) + signed_value = va_arg(parameters, ptrdiff_t); + else + goto incomprehensible_conversion; + value = (negative_value = signed_value < 0) ? + - (uintmax_t) signed_value : signed_value; + } + else + { + if ( length == LENGTH_SHORT_SHORT ) + value = va_arg(parameters, unsigned int); + else if ( length == LENGTH_SHORT ) + value = va_arg(parameters, unsigned int); + else if ( length == LENGTH_DEFAULT ) + value = va_arg(parameters, unsigned int); + else if ( length == LENGTH_LONG ) + value = va_arg(parameters, unsigned long); + else if ( length == LENGTH_LONG_LONG ) + value = va_arg(parameters, unsigned long long); + else if ( length == LENGTH_INTMAX_T ) + value = va_arg(parameters, uintmax_t); + else if ( length == LENGTH_SIZE_T ) + value = va_arg(parameters, size_t); + else if ( length == LENGTH_PTRDIFF_T ) + value = (uintmax_t) va_arg(parameters, ptrdiff_t); + else + goto incomprehensible_conversion; + } + + const char* digits = conversion == 'X' ? "0123456789ABCDEF" : + "0123456789abcdef"; + uintmax_t base = (conversion == 'x' || conversion == 'X') ? 16 : + conversion == 'o' ? 8 : 10; + char prefix[3]; + size_t prefix_length = 0; + if ( negative_value ) + prefix[prefix_length++] = '-'; + else if ( prepend_plus_if_positive ) + prefix[prefix_length++] = '+'; + else if ( prepend_blank_if_positive ) + prefix[prefix_length++] = ' '; + if ( alternate && (conversion == 'x' || conversion == 'X') ) + prefix[prefix_length++] = '0', + prefix[prefix_length++] = conversion; + if ( alternate && conversion == 'o' && value != 0 ) + prefix[prefix_length++] = '0'; + + char output[sizeof(uintmax_t) * 3]; + size_t output_length = convert_integer(output, value, base, digits); + size_t output_length_with_precision = + precision != SIZE_MAX && output_length < precision ? + precision : + output_length; + + size_t normal_length = prefix_length + output_length; + size_t length_with_precision = prefix_length + output_length_with_precision; + + bool use_precision = precision != SIZE_MAX; + bool use_zero_pad = zero_pad && 0 <= field_width && !use_precision; + bool use_left_pad = !use_zero_pad && 0 <= field_width; + bool use_right_pad = !use_zero_pad && field_width < 0; + + char c; + if ( use_left_pad ) + for ( size_t i = length_with_precision; i < abs_field_width; i++ ) + if ( callback(user, &(c = ' '), 1) != 1 ) + return SIZE_MAX; + else + written++; + if ( callback(user, prefix, prefix_length) != prefix_length ) + return SIZE_MAX; + written += prefix_length; + if ( use_zero_pad ) + for ( size_t i = normal_length; i < abs_field_width; i++ ) + if ( callback(user, &(c = '0'), 1) != 1 ) + return SIZE_MAX; + else + written++; + if ( use_precision ) + for ( size_t i = normal_length; i < precision; i++ ) + if ( callback(user, &(c = '0'), 1) != 1 ) + return SIZE_MAX; + else + written++; + if ( callback(user, output, output_length) != output_length ) + return SIZE_MAX; + written += output_length; + if ( use_right_pad ) + for ( size_t i = length_with_precision; i < abs_field_width; i++ ) + if ( callback(user, &(c = ' '), 1) != 1 ) + return SIZE_MAX; + else + written++; + } + else if ( *format == 'e' || *format == 'E' || + *format == 'f' || *format == 'F' || + *format == 'g' || *format == 'G' || + *format == 'a' || *format == 'A' ) + { + char conversion = *format++; + + long double value; + if ( length == LENGTH_DEFAULT ) + value = va_arg(parameters, double); + else if ( length == LENGTH_LONG_DOUBLE ) + value = va_arg(parameters, long double); + else + goto incomprehensible_conversion; + + // TODO: Implement floating-point printing. + (void) conversion; + (void) value; + + goto unsupported_conversion; + } + else if ( *format == 'c' && (format++, true) ) + { + char c; + if ( length == LENGTH_DEFAULT ) + c = (char) va_arg(parameters, int); + else if ( length == LENGTH_LONG ) + { + // TODO: Implement wide character printing. + (void) va_arg(parameters, wint_t); + goto unsupported_conversion; + } + else + goto incomprehensible_conversion; + + if ( callback(user, &c, 1) != 1 ) + return SIZE_MAX; + written++; + } + else if ( *format == 's' && (format++, true) ) + { + const char* string; + if ( length == LENGTH_DEFAULT ) + string = va_arg(parameters, const char*); + else if ( length == LENGTH_LONG ) + { + // TODO: Implement wide character string printing. + (void) va_arg(parameters, const wchar_t*); + goto unsupported_conversion; + } + else + goto incomprehensible_conversion; + + size_t string_length = 0; + for ( size_t i = 0; i < precision && string[i]; i++ ) + string_length++; + + if ( callback(user, string, string_length) != string_length ) + return SIZE_MAX; + written += string_length; + + } + else if ( *format == 'n' && (format++, true) ) + { + if ( length == LENGTH_SHORT_SHORT ) + *va_arg(parameters, signed char*) = (signed char) written; + else if ( length == LENGTH_SHORT ) + *va_arg(parameters, short*) = (short) written; + else if ( length == LENGTH_DEFAULT ) + *va_arg(parameters, int*) = (int) written; + else if ( length == LENGTH_LONG ) + *va_arg(parameters, long*) = (long) written; + else if ( length == LENGTH_LONG_LONG ) + *va_arg(parameters, long long*) = (long long) written; + else if ( length == LENGTH_INTMAX_T ) + *va_arg(parameters, uintmax_t*) = (uintmax_t) written; + else if ( length == LENGTH_SIZE_T ) + *va_arg(parameters, size_t*) = (size_t) written; + else if ( length == LENGTH_PTRDIFF_T ) + *va_arg(parameters, ptrdiff_t*) = (ptrdiff_t) written; + else + goto incomprehensible_conversion; + } + else + goto incomprehensible_conversion; + } + + return written; +}