From 86d458fc6c7af28aba55490ec0124585acfa338a Mon Sep 17 00:00:00 2001 From: "Justin C. Miller" Date: Wed, 1 Feb 2023 22:25:17 -0800 Subject: [PATCH] [util] Move remaining *printf impementations to util::format I added util::format as a replacement for other printf implementations last year, but it sat there only being used by the kernel all this time. Now I've templated it so that it can be used by the bootloader, and removed printf from panic.serial as well. --- src/boot/console.cpp | 122 +-- src/boot/console.h | 4 - src/kernel/panic.serial/display.cpp | 17 +- src/kernel/panic.serial/serial.module | 1 - src/kernel/printf/printf.c | 1184 ------------------------- src/kernel/printf/printf.h | 151 ---- src/kernel/printf/printf_config.h | 10 - src/libraries/util/format.cpp | 127 ++- src/libraries/util/util/format.h | 7 +- 9 files changed, 105 insertions(+), 1518 deletions(-) delete mode 100644 src/kernel/printf/printf.c delete mode 100644 src/kernel/printf/printf.h delete mode 100644 src/kernel/printf/printf_config.h diff --git a/src/boot/console.cpp b/src/boot/console.cpp index b0f0c77..7958eb7 100644 --- a/src/boot/console.cpp +++ b/src/boot/console.cpp @@ -3,6 +3,7 @@ #include #include +#include #include "console.h" #include "error.h" @@ -58,130 +59,13 @@ console::announce() m_out->output_string(L" booting...\r\n"); } -size_t -console::print_hex(uint32_t n) const -{ - wchar_t buffer[9]; - wchar_t *p = buffer; - for (int i = 7; i >= 0; --i) { - uint8_t nibble = (n >> (i*4)) & 0xf; - *p++ = digits[nibble]; - } - *p = 0; - m_out->output_string(buffer); - return 8; -} - -size_t -console::print_long_hex(uint64_t n) const -{ - wchar_t buffer[17]; - wchar_t *p = buffer; - for (int i = 15; i >= 0; --i) { - uint8_t nibble = (n >> (i*4)) & 0xf; - *p++ = digits[nibble]; - } - *p = 0; - m_out->output_string(buffer); - return 16; -} - -size_t -console::print_dec(uint32_t n) const -{ - wchar_t buffer[11]; - wchar_t *p = buffer + 10; - *p-- = 0; - do { - *p-- = digits[n % 10]; - n /= 10; - } while (n != 0); - - m_out->output_string(++p); - return 10 - (p - buffer); -} - -size_t -console::print_long_dec(uint64_t n) const -{ - wchar_t buffer[21]; - wchar_t *p = buffer + 20; - *p-- = 0; - do { - *p-- = digits[n % 10]; - n /= 10; - } while (n != 0); - - m_out->output_string(++p); - return 20 - (p - buffer); -} - size_t console::vprintf(const wchar_t *fmt, va_list args) const { wchar_t buffer[256]; - const wchar_t *r = fmt; - wchar_t *w = buffer; - size_t count = 0; + util::counted output {buffer, sizeof(buffer)/sizeof(wchar_t)}; + size_t count = util::vformat(output, fmt, args); - while (r && *r) { - if (*r != L'%') { - count++; - *w++ = *r++; - continue; - } - - *w = 0; - m_out->output_string(buffer); - w = buffer; - - r++; // chomp the % - - switch (*r++) { - case L'%': - m_out->output_string(const_cast(L"%")); - count++; - break; - - case L'x': - count += print_hex(va_arg(args, uint32_t)); - break; - - case L'd': - case L'u': - count += print_dec(va_arg(args, uint32_t)); - break; - - case L's': - { - wchar_t *s = va_arg(args, wchar_t*); - count += wstrlen(s); - m_out->output_string(s); - } - break; - - case L'l': - switch (*r++) { - case L'x': - count += print_long_hex(va_arg(args, uint64_t)); - break; - - case L'd': - case L'u': - count += print_long_dec(va_arg(args, uint64_t)); - break; - - default: - break; - } - break; - - default: - break; - } - } - - *w = 0; m_out->output_string(buffer); return count; } diff --git a/src/boot/console.h b/src/boot/console.h index 7411dce..c2ced45 100644 --- a/src/boot/console.h +++ b/src/boot/console.h @@ -21,10 +21,6 @@ public: void announce(); - size_t print_hex(uint32_t n) const; - size_t print_dec(uint32_t n) const; - size_t print_long_hex(uint64_t n) const; - size_t print_long_dec(uint64_t n) const; size_t printf(const wchar_t *fmt, ...) const; static console & get() { return *s_console; } diff --git a/src/kernel/panic.serial/display.cpp b/src/kernel/panic.serial/display.cpp index a53cc08..7568530 100644 --- a/src/kernel/panic.serial/display.cpp +++ b/src/kernel/panic.serial/display.cpp @@ -1,4 +1,4 @@ -#include "printf/printf.h" +#include #include "cpu.h" #include "display.h" @@ -29,7 +29,7 @@ print_header( out.write(":"); char linestr[6]; - snprintf(linestr, sizeof(linestr), "%ld", line); + util::format({linestr, sizeof(linestr)}, "%ld", line); out.write(linestr); } @@ -38,7 +38,7 @@ print_cpu(serial_port &out, cpu_data &cpu) { out.write("\n \e[0;31m==[ CPU: "); char cpuid[7]; - snprintf(cpuid, sizeof(cpuid), "%4x", cpu.id); + util::format({cpuid, sizeof(cpuid)}, "%4x", cpu.id); out.write(cpuid); out.write(" ]====================================================================\n"); } @@ -54,7 +54,7 @@ print_callstack(serial_port &out, symbol_table &syms, frame const *fp) if (!name) name = ""; - snprintf(message, sizeof(message), + util::format({message, sizeof(message)}, " \e[0;33mframe %2d: <0x%016lx> \e[1;33m%s\n", count++, fp->return_addr, name); @@ -68,7 +68,7 @@ print_reg(serial_port &out, const char *name, uint64_t val, const char *color) { char message[512]; - snprintf(message, sizeof(message), " \e[0;%sm%-3s %016lx\e[0m", color, name, val); + util::format({message, sizeof(message)}, " \e[0;%sm%3s %016lx\e[0m", color, name, val); out.write(message); } @@ -124,11 +124,11 @@ print_rip(serial_port &out, uintptr_t addr) out.write("\n"); for (unsigned i = 0; i < per_line*3; i += per_line) { - snprintf(bit, sizeof(bit), "\e[0;33m%20lx: \e[0m", addr + i); + util::format({bit, sizeof(bit)}, "\e[0;33m%20lx: \e[0m", addr + i); out.write(bit); for (unsigned j = 0; j < per_line; ++j) { - snprintf(bit, sizeof(bit), "%02x ", data[i+j]); + util::format({bit, sizeof(bit)}, "%02x ", data[i+j]); out.write(bit); } out.write("\n"); @@ -155,6 +155,3 @@ print_user_state(serial_port &out, const cpu_state ®s) } } // namespace panicking - -// For printf.c -extern "C" void putchar_(char c) {} diff --git a/src/kernel/panic.serial/serial.module b/src/kernel/panic.serial/serial.module index c405598..357a196 100644 --- a/src/kernel/panic.serial/serial.module +++ b/src/kernel/panic.serial/serial.module @@ -13,5 +13,4 @@ panic = module("panic.serial", "main.cpp", "serial.cpp", "symbol_table.cpp", - "../printf/printf.c", ]) diff --git a/src/kernel/printf/printf.c b/src/kernel/printf/printf.c deleted file mode 100644 index 1295694..0000000 --- a/src/kernel/printf/printf.c +++ /dev/null @@ -1,1184 +0,0 @@ -/** - * @author (c) Eyal Rozenberg - * 2021, Haifa, Palestine/Israel - * @author (c) Marco Paland (info@paland.com) - * 2014-2019, PALANDesign Hannover, Germany - * - * @note Others have made smaller contributions to this file: see the - * contributors page at https://github.com/eyalroz/printf/graphs/contributors - * or ask one of the authors. - * - * @brief Small stand-alone implementation of the printf family of functions - * (`(v)printf`, `(v)s(n)printf` etc., geared towards use on embedded systems with - * a very limited resources. - * - * @note the implementations are thread-safe; re-entrant; use no functions from - * the standard library; and do not dynamically allocate any memory. - * - * @license The MIT License (MIT) - * - * 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 -#include - -// Define this globally (e.g. gcc -DPRINTF_INCLUDE_CONFIG_H ...) to include the -// printf_config.h header file -#ifndef PRINTF_INCLUDE_CONFIG_H -#define PRINTF_INCLUDE_CONFIG_H 0 -#endif - -#if PRINTF_INCLUDE_CONFIG_H -#include "printf_config.h" -#endif - -#include - -#if PRINTF_ALIAS_STANDARD_FUNCTION_NAMES -# define printf_ printf -# define sprintf_ sprintf -# define vsprintf_ vsprintf -# define snprintf_ snprintf -# define vsnprintf_ vsnprintf -# define vprintf_ vprintf -#endif - - -// 'ntoa' conversion buffer size, this must be big enough to hold one converted -// numeric number including padded zeros (dynamically created on stack) -#ifndef PRINTF_INTEGER_BUFFER_SIZE -#define PRINTF_INTEGER_BUFFER_SIZE 32 -#endif - -// 'ftoa' conversion buffer size, this must be big enough to hold one converted -// float number including padded zeros (dynamically created on stack) -#ifndef PRINTF_FTOA_BUFFER_SIZE -#define PRINTF_FTOA_BUFFER_SIZE 32 -#endif - -// Support for the decimal notation floating point conversion specifiers (%f, %F) -#ifndef PRINTF_SUPPORT_DECIMAL_SPECIFIERS -#define PRINTF_SUPPORT_DECIMAL_SPECIFIERS 1 -#endif - -// Support for the exponential notation floating point conversion specifiers (%e, %g, %E, %G) -#ifndef PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS -#define PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS 1 -#endif - -// Support for the length write-back specifier (%n) -#ifndef PRINTF_SUPPORT_WRITEBACK_SPECIFIER -#define PRINTF_SUPPORT_WRITEBACK_SPECIFIER 1 -#endif - -// Default precision for the floating point conversion specifiers (the C standard sets this at 6) -#ifndef PRINTF_DEFAULT_FLOAT_PRECISION -#define PRINTF_DEFAULT_FLOAT_PRECISION 6 -#endif - -// According to the C languages standard, printf() and related functions must be able to print any -// integral number in floating-point notation, regardless of length, when using the %f specifier - -// possibly hundreds of characters, potentially overflowing your buffers. In this implementation, -// all values beyond this threshold are switched to exponential notation. -#ifndef PRINTF_MAX_INTEGRAL_DIGITS_FOR_DECIMAL -#define PRINTF_MAX_INTEGRAL_DIGITS_FOR_DECIMAL 9 -#endif - -// Support for the long long integral types (with the ll, z and t length modifiers for specifiers -// %d,%i,%o,%x,%X,%u, and with the %p specifier). Note: 'L' (long double) is not supported. -#ifndef PRINTF_SUPPORT_LONG_LONG -#define PRINTF_SUPPORT_LONG_LONG 1 -#endif - -#if PRINTF_SUPPORT_LONG_LONG -typedef unsigned long long printf_unsigned_value_t; -typedef long long printf_signed_value_t; -#else -typedef unsigned long printf_unsigned_value_t; -typedef long printf_signed_value_t; -#endif - -#define PRINTF_PREFER_DECIMAL false -#define PRINTF_PREFER_EXPONENTIAL true - -/////////////////////////////////////////////////////////////////////////////// - -// The following will convert the number-of-digits into an exponential-notation literal -#define PRINTF_CONCATENATE(s1, s2) s1##s2 -#define PRINTF_EXPAND_THEN_CONCATENATE(s1, s2) PRINTF_CONCATENATE(s1, s2) -#define PRINTF_FLOAT_NOTATION_THRESHOLD PRINTF_EXPAND_THEN_CONCATENATE(1e,PRINTF_MAX_INTEGRAL_DIGITS_FOR_DECIMAL) - -// internal flag definitions -#define FLAGS_ZEROPAD (1U << 0U) -#define FLAGS_LEFT (1U << 1U) -#define FLAGS_PLUS (1U << 2U) -#define FLAGS_SPACE (1U << 3U) -#define FLAGS_HASH (1U << 4U) -#define FLAGS_UPPERCASE (1U << 5U) -#define FLAGS_CHAR (1U << 6U) -#define FLAGS_SHORT (1U << 7U) -#define FLAGS_LONG (1U << 8U) -#define FLAGS_LONG_LONG (1U << 9U) -#define FLAGS_PRECISION (1U << 10U) -#define FLAGS_ADAPT_EXP (1U << 11U) -#define FLAGS_POINTER (1U << 12U) -// Note: Similar, but not identical, effect as FLAGS_HASH - -#define BASE_BINARY 2 -#define BASE_OCTAL 8 -#define BASE_DECIMAL 10 -#define BASE_HEX 16 - -typedef uint8_t numeric_base_t; - -#if (PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS) -#include -#if FLT_RADIX != 2 -#error "Non-binary-radix floating-point types are unsupported." -#endif - -#if DBL_MANT_DIG == 24 - -#define DOUBLE_SIZE_IN_BITS 32 -typedef uint32_t double_uint_t; -#define DOUBLE_EXPONENT_MASK 0xFFU -#define DOUBLE_BASE_EXPONENT 127 - -#elif DBL_MANT_DIG == 53 - -#define DOUBLE_SIZE_IN_BITS 64 -typedef uint64_t double_uint_t; -#define DOUBLE_EXPONENT_MASK 0x7FFU -#define DOUBLE_BASE_EXPONENT 1023 - -#else -#error "Unsupported double type configuration" -#endif -#define DOUBLE_STORED_MANTISSA_BITS (DBL_MANT_DIG - 1) - -typedef union { - double_uint_t U; - double F; -} double_with_bit_access; - -// This is unnecessary in C99, since compound initializers can be used, -// but: 1. Some compilers are finicky about this; 2. Some people may want to convert this to C89; -// 3. If you try to use it as C++, only C++20 supports compound literals -static inline double_with_bit_access get_bit_access(double x) -{ - double_with_bit_access dwba; - dwba.F = x; - return dwba; -} - -static inline int get_sign(double x) -{ - // The sign is stored in the highest bit - return get_bit_access(x).U >> (DOUBLE_SIZE_IN_BITS - 1); -} - -static inline int get_exp2(double_with_bit_access x) -{ - // The exponent in an IEEE-754 floating-point number occupies a contiguous - // sequence of bits (e.g. 52..62 for 64-bit doubles), but with a non-trivial representation: An - // unsigned offset from some negative value (with the extremal offset values reserved for - // special use). - return (int)((x.U >> DOUBLE_STORED_MANTISSA_BITS ) & DOUBLE_EXPONENT_MASK) - DOUBLE_BASE_EXPONENT; -} -#define PRINTF_ABS(_x) ( (_x) > 0 ? (_x) : -(_x) ) - -#endif // (PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS) - -// Note in particular the behavior here on LONG_MIN or LLONG_MIN; it is valid -// and well-defined, but if you're not careful you can easily trigger undefined -// behavior with -LONG_MIN or -LLONG_MIN -#define ABS_FOR_PRINTING(_x) ((printf_unsigned_value_t) ( (_x) > 0 ? (_x) : -((printf_signed_value_t)_x) )) - -// output function type -typedef void (*out_fct_type)(char character, void* buffer, size_t idx, size_t maxlen); - - -// wrapper (used as buffer) for output function type -typedef struct { - void (*fct)(char character, void* arg); - void* arg; -} out_function_wrapper_type; - - -// internal buffer output -static inline void out_buffer(char character, void* buffer, size_t idx, size_t maxlen) -{ - if (idx < maxlen) { - ((char*)buffer)[idx] = character; - } -} - - -// internal null output -static inline void out_discard(char character, void* buffer, size_t idx, size_t maxlen) -{ - (void)character; (void)buffer; (void)idx; (void)maxlen; -} - - -// internal putchar_ wrapper -static inline void out_putchar(char character, void* buffer, size_t idx, size_t maxlen) -{ - (void)buffer; (void)idx; (void)maxlen; - if (character) { - putchar_(character); - } -} - - -// internal output function wrapper -static inline void out_wrapped_function(char character, void* wrapped_function, size_t idx, size_t maxlen) -{ - (void)idx; (void)maxlen; - if (character) { - // buffer is the output fct pointer - ((out_function_wrapper_type*)wrapped_function)->fct(character, ((out_function_wrapper_type*)wrapped_function)->arg); - } -} - - -// internal secure strlen -// @return The length of the string (excluding the terminating 0) limited by 'maxsize' -static inline unsigned int strnlen_s_(const char* str, size_t maxsize) -{ - const char* s; - for (s = str; *s && maxsize--; ++s); - return (unsigned int)(s - str); -} - - -// internal test if char is a digit (0-9) -// @return true if char is a digit -static inline bool is_digit_(char ch) -{ - return (ch >= '0') && (ch <= '9'); -} - - -// internal ASCII string to unsigned int conversion -static unsigned int atoi_(const char** str) -{ - unsigned int i = 0U; - while (is_digit_(**str)) { - i = i * 10U + (unsigned int)(*((*str)++) - '0'); - } - return i; -} - - -// output the specified string in reverse, taking care of any zero-padding -static size_t out_rev_(out_fct_type out, char* buffer, size_t idx, size_t maxlen, const char* buf, size_t len, unsigned int width, unsigned int flags) -{ - const size_t start_idx = idx; - - // pad spaces up to given width - if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) { - for (size_t i = len; i < width; i++) { - out(' ', buffer, idx++, maxlen); - } - } - - // reverse string - while (len) { - out(buf[--len], buffer, idx++, maxlen); - } - - // append pad spaces up to given width - if (flags & FLAGS_LEFT) { - while (idx - start_idx < width) { - out(' ', buffer, idx++, maxlen); - } - } - - return idx; -} - - -// Invoked by print_integer after the actual number has been printed, performing necessary -// work on the number's prefix (as the number is initially printed in reverse order) -static size_t print_integer_finalization(out_fct_type out, char* buffer, size_t idx, size_t maxlen, char* buf, size_t len, bool negative, numeric_base_t base, unsigned int precision, unsigned int width, unsigned int flags) -{ - size_t unpadded_len = len; - - // pad with leading zeros - { - if (!(flags & FLAGS_LEFT)) { - if (width && (flags & FLAGS_ZEROPAD) && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { - width--; - } - while ((flags & FLAGS_ZEROPAD) && (len < width) && (len < PRINTF_INTEGER_BUFFER_SIZE)) { - buf[len++] = '0'; - } - } - - while ((len < precision) && (len < PRINTF_INTEGER_BUFFER_SIZE)) { - buf[len++] = '0'; - } - - if (base == BASE_OCTAL && (len > unpadded_len)) { - // Since we've written some zeros, we've satisfied the alternative format leading space requirement - flags &= ~FLAGS_HASH; - } - } - - // handle hash - if (flags & (FLAGS_HASH | FLAGS_POINTER)) { - if (!(flags & FLAGS_PRECISION) && len && ((len == precision) || (len == width))) { - // Let's take back some padding digits to fit in what will eventually - // be the format-specific prefix - if (unpadded_len < len) { - len--; - } - if (len && (base == BASE_HEX)) { - if (unpadded_len < len) { - len--; - } - } - } - if ((base == BASE_HEX) && !(flags & FLAGS_UPPERCASE) && (len < PRINTF_INTEGER_BUFFER_SIZE)) { - buf[len++] = 'x'; - } - else if ((base == BASE_HEX) && (flags & FLAGS_UPPERCASE) && (len < PRINTF_INTEGER_BUFFER_SIZE)) { - buf[len++] = 'X'; - } - else if ((base == BASE_BINARY) && (len < PRINTF_INTEGER_BUFFER_SIZE)) { - buf[len++] = 'b'; - } - if (len < PRINTF_INTEGER_BUFFER_SIZE) { - buf[len++] = '0'; - } - } - - if (len < PRINTF_INTEGER_BUFFER_SIZE) { - if (negative) { - buf[len++] = '-'; - } - else if (flags & FLAGS_PLUS) { - buf[len++] = '+'; // ignore the space if the '+' exists - } - else if (flags & FLAGS_SPACE) { - buf[len++] = ' '; - } - } - - return out_rev_(out, buffer, idx, maxlen, buf, len, width, flags); -} - -// An internal itoa-like function -static size_t print_integer(out_fct_type out, char* buffer, size_t idx, size_t maxlen, printf_unsigned_value_t value, bool negative, numeric_base_t base, unsigned int precision, unsigned int width, unsigned int flags) -{ - char buf[PRINTF_INTEGER_BUFFER_SIZE]; - size_t len = 0U; - - if (!value) { - if ( !(flags & FLAGS_PRECISION) ) { - buf[len++] = '0'; - flags &= ~FLAGS_HASH; - // We drop this flag this since either the alternative and regular modes of the specifier - // don't differ on 0 values, or (in the case of octal) we've already provided the special - // handling for this mode. - } - else if (base == BASE_HEX) { - flags &= ~FLAGS_HASH; - // We drop this flag this since either the alternative and regular modes of the specifier - // don't differ on 0 values - } - } - else { - do { - const char digit = (char)(value % base); - buf[len++] = (char)(digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10); - value /= base; - } while (value && (len < PRINTF_INTEGER_BUFFER_SIZE)); - } - - return print_integer_finalization(out, buffer, idx, maxlen, buf, len, negative, base, precision, width, flags); -} - -#if (PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS) - -struct double_components { - int_fast64_t integral; - int_fast64_t fractional; - bool is_negative; -}; - -#define NUM_DECIMAL_DIGITS_IN_INT64_T 18 -#define PRINTF_MAX_PRECOMPUTED_POWER_OF_10 NUM_DECIMAL_DIGITS_IN_INT64_T -static const double powers_of_10[NUM_DECIMAL_DIGITS_IN_INT64_T] = { - 1e00, 1e01, 1e02, 1e03, 1e04, 1e05, 1e06, 1e07, 1e08, - 1e09, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17 -}; - -#define PRINTF_MAX_SUPPORTED_PRECISION NUM_DECIMAL_DIGITS_IN_INT64_T - 1 - - -// Break up a double number - which is known to be a finite non-negative number - -// into its base-10 parts: integral - before the decimal point, and fractional - after it. -// Taken the precision into account, but does not change it even internally. -static struct double_components get_components(double number, unsigned int precision) -{ - struct double_components number_; - number_.is_negative = get_sign(number); - double abs_number = (number_.is_negative) ? -number : number; - number_.integral = (int_fast64_t)abs_number; - double remainder = (abs_number - number_.integral) * powers_of_10[precision]; - number_.fractional = (int_fast64_t)remainder; - - remainder -= (double) number_.fractional; - - if (remainder > 0.5) { - ++number_.fractional; - // handle rollover, e.g. case 0.99 with precision 1 is 1.0 - if ((double) number_.fractional >= powers_of_10[precision]) { - number_.fractional = 0; - ++number_.integral; - } - } - else if (remainder == 0.5) { - if ((number_.fractional == 0U) || (number_.fractional & 1U)) { - // if halfway, round up if odd OR if last digit is 0 - ++number_.fractional; - } - } - - if (precision == 0U) { - remainder = abs_number - (double) number_.integral; - if ((!(remainder < 0.5) || (remainder > 0.5)) && (number_.integral & 1)) { - // exactly 0.5 and ODD, then round up - // 1.5 -> 2, but 2.5 -> 2 - ++number_.integral; - } - } - return number_; -} - -struct scaling_factor { - double raw_factor; - bool multiply; // if true, need to multiply by raw_factor; otherwise need to divide by it -}; - -double apply_scaling(double num, struct scaling_factor normalization) -{ - return normalization.multiply ? num * normalization.raw_factor : num / normalization.raw_factor; -} - -double unapply_scaling(double normalized, struct scaling_factor normalization) -{ - return normalization.multiply ? normalized / normalization.raw_factor : normalized * normalization.raw_factor; -} - -struct scaling_factor update_normalization(struct scaling_factor sf, double extra_multiplicative_factor) -{ - struct scaling_factor result; - if (sf.multiply) { - result.multiply = true; - result.raw_factor = sf.raw_factor * extra_multiplicative_factor; - } - else { - int factor_exp2 = get_exp2(get_bit_access(sf.raw_factor)); - int extra_factor_exp2 = get_exp2(get_bit_access(extra_multiplicative_factor)); - - // Divide the larger-exponent raw raw_factor by the smaller - if (PRINTF_ABS(factor_exp2) > PRINTF_ABS(extra_factor_exp2)) { - result.multiply = false; - result.raw_factor = sf.raw_factor / extra_multiplicative_factor; - } - else { - result.multiply = true; - result.raw_factor = extra_multiplicative_factor / sf.raw_factor; - } - } - return result; -} - -#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS -static struct double_components get_normalized_components(bool negative, unsigned int precision, double non_normalized, struct scaling_factor normalization) -{ - struct double_components components; - components.is_negative = negative; - components.integral = (int_fast64_t) apply_scaling(non_normalized, normalization); - double remainder = non_normalized - unapply_scaling((double) components.integral, normalization); - double prec_power_of_10 = powers_of_10[precision]; - struct scaling_factor account_for_precision = update_normalization(normalization, prec_power_of_10); - double scaled_remainder = apply_scaling(remainder, account_for_precision); - double rounding_threshold = 0.5; - - if (precision == 0U) { - components.fractional = 0; - components.integral += (scaled_remainder >= rounding_threshold); - if (scaled_remainder == rounding_threshold) { - // banker's rounding: Round towards the even number (making the mean error 0) - components.integral &= ~((int_fast64_t) 0x1); - } - } - else { - components.fractional = (int_fast64_t) scaled_remainder; - scaled_remainder -= components.fractional; - - components.fractional += (scaled_remainder >= rounding_threshold); - if (scaled_remainder == rounding_threshold) { - // banker's rounding: Round towards the even number (making the mean error 0) - components.fractional &= ~((int_fast64_t) 0x1); - } - // handle rollover, e.g. the case of 0.99 with precision 1 becoming (0,100), - // and must then be corrected into (1, 0). - if ((double) components.fractional >= prec_power_of_10) { - components.fractional = 0; - ++components.integral; - } - } - return components; -} -#endif - -static size_t print_broken_up_decimal( - struct double_components number_, out_fct_type out, char *buffer, size_t idx, size_t maxlen, unsigned int precision, - unsigned int width, unsigned int flags, char *buf, size_t len) -{ - if (precision != 0U) { - // do fractional part, as an unsigned number - - unsigned int count = precision; - - if (flags & FLAGS_ADAPT_EXP && !(flags & FLAGS_HASH)) { - // %g/%G mandates we skip the trailing 0 digits... - if (number_.fractional > 0) { - while(true) { - int_fast64_t digit = number_.fractional % 10U; - if (digit != 0) { - break; - } - --count; - number_.fractional /= 10U; - } - - } - // ... and even the decimal point if there are no - // non-zero fractional part digits (see below) - } - - if (number_.fractional > 0 || !(flags & FLAGS_ADAPT_EXP) || (flags & FLAGS_HASH) ) { - while (len < PRINTF_FTOA_BUFFER_SIZE) { - --count; - buf[len++] = (char)('0' + number_.fractional % 10U); - if (!(number_.fractional /= 10U)) { - break; - } - } - // add extra 0s - while ((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0U)) { - buf[len++] = '0'; - } - if (len < PRINTF_FTOA_BUFFER_SIZE) { - buf[len++] = '.'; - } - } - } - else { - if (flags & FLAGS_HASH) { - if (len < PRINTF_FTOA_BUFFER_SIZE) { - buf[len++] = '.'; - } - } - } - - // Write the integer part of the number (it comes after the fractional - // since the character order is reversed) - while (len < PRINTF_FTOA_BUFFER_SIZE) { - buf[len++] = (char)('0' + (number_.integral % 10)); - if (!(number_.integral /= 10)) { - break; - } - } - - // pad leading zeros - if (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) { - if (width && (number_.is_negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { - width--; - } - while ((len < width) && (len < PRINTF_FTOA_BUFFER_SIZE)) { - buf[len++] = '0'; - } - } - - if (len < PRINTF_FTOA_BUFFER_SIZE) { - if (number_.is_negative) { - buf[len++] = '-'; - } - else if (flags & FLAGS_PLUS) { - buf[len++] = '+'; // ignore the space if the '+' exists - } - else if (flags & FLAGS_SPACE) { - buf[len++] = ' '; - } - } - - return out_rev_(out, buffer, idx, maxlen, buf, len, width, flags); -} - - // internal ftoa for fixed decimal floating point -static size_t print_decimal_number(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double number, unsigned int precision, unsigned int width, unsigned int flags, char* buf, size_t len) -{ - struct double_components value_ = get_components(number, precision); - return print_broken_up_decimal(value_, out, buffer, idx, maxlen, precision, width, flags, buf, len); -} - -#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS -// internal ftoa variant for exponential floating-point type, contributed by Martijn Jasperse -static size_t print_exponential_number(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double number, unsigned int precision, unsigned int width, unsigned int flags, char* buf, size_t len) -{ - const bool negative = get_sign(number); - // This number will decrease gradually (by factors of 10) as we "extract" the exponent out of it - double abs_number = negative ? -number : number; - - int exp10; - bool abs_exp10_covered_by_powers_table; - struct scaling_factor normalization; - - - // Determine the decimal exponent - if (abs_number == 0.0) { - // TODO: This is a special-case for 0.0 (and -0.0); but proper handling is required for denormals more generally. - exp10 = 0; // ... and no need to set a normalization factor or check the powers table - } - else { - double_with_bit_access conv = get_bit_access(abs_number); - { - // based on the algorithm by David Gay (https://www.ampl.com/netlib/fp/dtoa.c) - int exp2 = get_exp2(conv); - // drop the exponent, so conv.F comes into the range [1,2) - conv.U = (conv.U & (( (double_uint_t)(1) << DOUBLE_STORED_MANTISSA_BITS) - 1U)) | ((double_uint_t) DOUBLE_BASE_EXPONENT << DOUBLE_STORED_MANTISSA_BITS); - // now approximate log10 from the log2 integer part and an expansion of ln around 1.5 - exp10 = (int)(0.1760912590558 + exp2 * 0.301029995663981 + (conv.F - 1.5) * 0.289529654602168); - // now we want to compute 10^exp10 but we want to be sure it won't overflow - exp2 = (int)(exp10 * 3.321928094887362 + 0.5); - const double z = exp10 * 2.302585092994046 - exp2 * 0.6931471805599453; - const double z2 = z * z; - conv.U = ((double_uint_t)(exp2) + DOUBLE_BASE_EXPONENT) << DOUBLE_STORED_MANTISSA_BITS; - // compute exp(z) using continued fractions, see https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex - conv.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14))))); - // correct for rounding errors - if (abs_number < conv.F) { - exp10--; - conv.F /= 10; - } - } - abs_exp10_covered_by_powers_table = PRINTF_ABS(exp10) < PRINTF_MAX_PRECOMPUTED_POWER_OF_10; - normalization.raw_factor = abs_exp10_covered_by_powers_table ? powers_of_10[PRINTF_ABS(exp10)] : conv.F; - } - - // We now begin accounting for the widths of the two parts of our printed field: - // the decimal part after decimal exponent extraction, and the base-10 exponent part. - // For both of these, the value of 0 has a special meaning, but not the same one: - // a 0 exponent-part width means "don't print the exponent"; a 0 decimal-part width - // means "use as many characters as necessary". - - bool fall_back_to_decimal_only_mode = false; - if (flags & FLAGS_ADAPT_EXP) { - int required_significant_digits = (precision == 0) ? 1 : (int) precision; - // Should we want to fall-back to "%f" mode, and only print the decimal part? - fall_back_to_decimal_only_mode = (exp10 >= -4 && exp10 < required_significant_digits); - // Now, let's adjust the precision - // This also decided how we adjust the precision value - as in "%g" mode, - // "precision" is the number of _significant digits_, and this is when we "translate" - // the precision value to an actual number of decimal digits. - int precision_ = (fall_back_to_decimal_only_mode) ? - (int) precision - 1 - exp10 : - (int) precision - 1; // the presence of the exponent ensures only one significant digit comes before the decimal point - precision = (precision_ > 0 ? (unsigned) precision_ : 0U); - flags |= FLAGS_PRECISION; // make sure print_broken_up_decimal respects our choice above - } - - normalization.multiply = (exp10 < 0 && abs_exp10_covered_by_powers_table); - bool should_skip_normalization = (fall_back_to_decimal_only_mode || exp10 == 0); - struct double_components decimal_part_components = - should_skip_normalization ? - get_components(negative ? -abs_number : abs_number, precision) : - get_normalized_components(negative, precision, abs_number, normalization); - - // Account for roll-over, e.g. rounding from 9.99 to 100.0 - which effects - // the exponent and may require additional tweaking of the parts - if (fall_back_to_decimal_only_mode) { - if ( (flags & FLAGS_ADAPT_EXP) && exp10 >= -1 && decimal_part_components.integral == powers_of_10[exp10 + 1]) { - exp10++; // Not strictly necessary, since exp10 is no longer really used - precision--; - // ... and it should already be the case that decimal_part_components.fractional == 0 - } - // TODO: What about rollover strictly within the fractional part? - } - else { - if (decimal_part_components.integral >= 10) { - exp10++; - decimal_part_components.integral = 1; - decimal_part_components.fractional = 0; - } - } - - // the exp10 format is "E%+03d" and largest possible exp10 value for a 64-bit double - // is "307" (for 2^1023), so we set aside 4-5 characters overall - unsigned int exp10_part_width = fall_back_to_decimal_only_mode ? 0U : (PRINTF_ABS(exp10) < 100) ? 4U : 5U; - - unsigned int decimal_part_width = - ((flags & FLAGS_LEFT) && exp10_part_width) ? - // We're padding on the right, so the width constraint is the exponent part's - // problem, not the decimal part's, so we'll use as many characters as we need: - 0U : - // We're padding on the left; so the width constraint is the decimal part's - // problem. Well, can both the decimal part and the exponent part fit within our overall width? - ((width > exp10_part_width) ? - // Yes, so we limit our decimal part's width. - // (Note this is trivially valid even if we've fallen back to "%f" mode) - width - exp10_part_width : - // No; we just give up on any restriction on the decimal part and use as many - // characters as we need - 0U); - - const size_t start_idx = idx; - idx = print_broken_up_decimal(decimal_part_components, out, buffer, idx, maxlen, precision, decimal_part_width, flags, buf, len); - - if (! fall_back_to_decimal_only_mode) { - out((flags & FLAGS_UPPERCASE) ? 'E' : 'e', buffer, idx++, maxlen); - idx = print_integer(out, buffer, idx, maxlen, - ABS_FOR_PRINTING(exp10), - exp10 < 0, 10, 0, exp10_part_width - 1, - FLAGS_ZEROPAD | FLAGS_PLUS); - if (flags & FLAGS_LEFT) { - // We need to right-pad with spaces to meet the width requirement - while (idx - start_idx < width) out(' ', buffer, idx++, maxlen); - } - } - return idx; -} -#endif // PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS - - -static size_t print_floating_point(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int precision, unsigned int width, unsigned int flags, bool prefer_exponential) -{ - char buf[PRINTF_FTOA_BUFFER_SIZE]; - size_t len = 0U; - - // test for special values - if (value != value) - return out_rev_(out, buffer, idx, maxlen, "nan", 3, width, flags); - if (value < -DBL_MAX) - return out_rev_(out, buffer, idx, maxlen, "fni-", 4, width, flags); - if (value > DBL_MAX) - return out_rev_(out, buffer, idx, maxlen, (flags & FLAGS_PLUS) ? "fni+" : "fni", (flags & FLAGS_PLUS) ? 4U : 3U, width, flags); - - if (!prefer_exponential && ((value > PRINTF_FLOAT_NOTATION_THRESHOLD) || (value < -PRINTF_FLOAT_NOTATION_THRESHOLD))) { - // The required behavior of standard printf is to print _every_ integral-part digit -- which could mean - // printing hundreds of characters, overflowing any fixed internal buffer and necessitating a more complicated - // implementation. -#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS - return print_exponential_number(out, buffer, idx, maxlen, value, precision, width, flags, buf, len); -#else - return 0U; -#endif - } - - // set default precision, if not set explicitly - if (!(flags & FLAGS_PRECISION)) { - precision = PRINTF_DEFAULT_FLOAT_PRECISION; - } - - // limit precision so that our integer holding the fractional part does not overflow - while ((len < PRINTF_FTOA_BUFFER_SIZE) && (precision > PRINTF_MAX_SUPPORTED_PRECISION)) { - buf[len++] = '0'; // This respects the precision in terms of result length only - precision--; - } - - return -#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS - prefer_exponential ? - print_exponential_number(out, buffer, idx, maxlen, value, precision, width, flags, buf, len) : -#endif - print_decimal_number(out, buffer, idx, maxlen, value, precision, width, flags, buf, len); -} - -#endif // (PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS) - -// internal vsnprintf -static int _vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const char* format, va_list va) -{ - unsigned int flags, width, precision, n; - size_t idx = 0U; - - if (!buffer) { - // use null output function - out = out_discard; - } - - while (*format) - { - // format specifier? %[flags][width][.precision][length] - if (*format != '%') { - // no - out(*format, buffer, idx++, maxlen); - format++; - continue; - } - else { - // yes, evaluate it - format++; - } - - // evaluate flags - flags = 0U; - do { - switch (*format) { - case '0': flags |= FLAGS_ZEROPAD; format++; n = 1U; break; - case '-': flags |= FLAGS_LEFT; format++; n = 1U; break; - case '+': flags |= FLAGS_PLUS; format++; n = 1U; break; - case ' ': flags |= FLAGS_SPACE; format++; n = 1U; break; - case '#': flags |= FLAGS_HASH; format++; n = 1U; break; - default : n = 0U; break; - } - } while (n); - - // evaluate width field - width = 0U; - if (is_digit_(*format)) { - width = atoi_(&format); - } - else if (*format == '*') { - const int w = va_arg(va, int); - if (w < 0) { - flags |= FLAGS_LEFT; // reverse padding - width = (unsigned int)-w; - } - else { - width = (unsigned int)w; - } - format++; - } - - // evaluate precision field - precision = 0U; - if (*format == '.') { - flags |= FLAGS_PRECISION; - format++; - if (is_digit_(*format)) { - precision = atoi_(&format); - } - else if (*format == '*') { - const int precision_ = (int)va_arg(va, int); - precision = precision_ > 0 ? (unsigned int)precision_ : 0U; - format++; - } - } - - // evaluate length field - switch (*format) { - case 'l' : - flags |= FLAGS_LONG; - format++; - if (*format == 'l') { - flags |= FLAGS_LONG_LONG; - format++; - } - break; - case 'h' : - flags |= FLAGS_SHORT; - format++; - if (*format == 'h') { - flags |= FLAGS_CHAR; - format++; - } - break; - case 't' : - flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); - format++; - break; - case 'j' : - flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); - format++; - break; - case 'z' : - flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); - format++; - break; - default: - break; - } - - // evaluate specifier - switch (*format) { - case 'd' : - case 'i' : - case 'u' : - case 'x' : - case 'X' : - case 'o' : - case 'b' : { - // set the base - numeric_base_t base; - if (*format == 'x' || *format == 'X') { - base = BASE_HEX; - } - else if (*format == 'o') { - base = BASE_OCTAL; - } - else if (*format == 'b') { - base = BASE_BINARY; - } - else { - base = BASE_DECIMAL; - flags &= ~FLAGS_HASH; // no hash for dec format - } - // uppercase - if (*format == 'X') { - flags |= FLAGS_UPPERCASE; - } - - // no plus or space flag for u, x, X, o, b - if ((*format != 'i') && (*format != 'd')) { - flags &= ~(FLAGS_PLUS | FLAGS_SPACE); - } - - // ignore '0' flag when precision is given - if (flags & FLAGS_PRECISION) { - flags &= ~FLAGS_ZEROPAD; - } - - // convert the integer - if ((*format == 'i') || (*format == 'd')) { - // signed - if (flags & FLAGS_LONG_LONG) { -#if PRINTF_SUPPORT_LONG_LONG - const long long value = va_arg(va, long long); - idx = print_integer(out, buffer, idx, maxlen, ABS_FOR_PRINTING(value), value < 0, base, precision, width, flags); -#endif - } - else if (flags & FLAGS_LONG) { - const long value = va_arg(va, long); - idx = print_integer(out, buffer, idx, maxlen, ABS_FOR_PRINTING(value), value < 0, base, precision, width, flags); - } - else { - const int value = (flags & FLAGS_CHAR) ? (signed char)va_arg(va, int) : (flags & FLAGS_SHORT) ? (short int)va_arg(va, int) : va_arg(va, int); - idx = print_integer(out, buffer, idx, maxlen, ABS_FOR_PRINTING(value), value < 0, base, precision, width, flags); - } - } - else { - // unsigned - if (flags & FLAGS_LONG_LONG) { -#if PRINTF_SUPPORT_LONG_LONG - idx = print_integer(out, buffer, idx, maxlen, (printf_unsigned_value_t) va_arg(va, unsigned long long), false, base, precision, width, flags); -#endif - } - else if (flags & FLAGS_LONG) { - idx = print_integer(out, buffer, idx, maxlen, (printf_unsigned_value_t) va_arg(va, unsigned long), false, base, precision, width, flags); - } - else { - const unsigned int value = (flags & FLAGS_CHAR) ? (unsigned char)va_arg(va, unsigned int) : (flags & FLAGS_SHORT) ? (unsigned short int)va_arg(va, unsigned int) : va_arg(va, unsigned int); - idx = print_integer(out, buffer, idx, maxlen, (printf_unsigned_value_t) value, false, base, precision, width, flags); - } - } - format++; - break; - } -#if PRINTF_SUPPORT_DECIMAL_SPECIFIERS - case 'f' : - case 'F' : - if (*format == 'F') flags |= FLAGS_UPPERCASE; - idx = print_floating_point(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags, PRINTF_PREFER_DECIMAL); - format++; - break; -#endif -#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS - case 'e': - case 'E': - case 'g': - case 'G': - if ((*format == 'g')||(*format == 'G')) flags |= FLAGS_ADAPT_EXP; - if ((*format == 'E')||(*format == 'G')) flags |= FLAGS_UPPERCASE; - idx = print_floating_point(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags, PRINTF_PREFER_EXPONENTIAL); - format++; - break; -#endif // PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS - case 'c' : { - unsigned int l = 1U; - // pre padding - if (!(flags & FLAGS_LEFT)) { - while (l++ < width) { - out(' ', buffer, idx++, maxlen); - } - } - // char output - out((char)va_arg(va, int), buffer, idx++, maxlen); - // post padding - if (flags & FLAGS_LEFT) { - while (l++ < width) { - out(' ', buffer, idx++, maxlen); - } - } - format++; - break; - } - - case 's' : { - const char* p = va_arg(va, char*); - if (p == NULL) { - idx = out_rev_(out, buffer, idx, maxlen, ")llun(", 6, width, flags); - } - else { - unsigned int l = strnlen_s_(p, precision ? precision : (size_t)-1); - // pre padding - if (flags & FLAGS_PRECISION) { - l = (l < precision ? l : precision); - } - if (!(flags & FLAGS_LEFT)) { - while (l++ < width) { - out(' ', buffer, idx++, maxlen); - } - } - // string output - while ((*p != 0) && (!(flags & FLAGS_PRECISION) || precision--)) { - out(*(p++), buffer, idx++, maxlen); - } - // post padding - if (flags & FLAGS_LEFT) { - while (l++ < width) { - out(' ', buffer, idx++, maxlen); - } - } - } - format++; - break; - } - - case 'p' : { - width = sizeof(void*) * 2U + 2; // 2 hex chars per byte + the "0x" prefix - flags |= FLAGS_ZEROPAD | FLAGS_POINTER; - uintptr_t value = (uintptr_t)va_arg(va, void*); - idx = (value == (uintptr_t) NULL) ? - out_rev_(out, buffer, idx, maxlen, ")lin(", 5, width, flags) : - print_integer(out, buffer, idx, maxlen, (printf_unsigned_value_t) value, false, BASE_HEX, precision, width, flags); - format++; - break; - } - - case '%' : - out('%', buffer, idx++, maxlen); - format++; - break; - - // Many people prefer to disable support for %n, as it lets the caller - // engineer a write to an arbitrary location, of a value the caller - // effectively controls - which could be a security concern in some cases. -#if PRINTF_SUPPORT_WRITEBACK_SPECIFIER - case 'n' : { - if (flags & FLAGS_CHAR) *(va_arg(va, char*)) = (char) idx; - else if (flags & FLAGS_SHORT) *(va_arg(va, short*)) = (short) idx; - else if (flags & FLAGS_LONG) *(va_arg(va, long*)) = (long) idx; -#if PRINTF_SUPPORT_LONG_LONG - else if (flags & FLAGS_LONG_LONG) *(va_arg(va, long long*)) = (long long int) idx; -#endif // PRINTF_SUPPORT_LONG_LONG - else *(va_arg(va, int*)) = (int) idx; - format++; - break; - } -#endif // PRINTF_SUPPORT_WRITEBACK_SPECIFIER - - default : - out(*format, buffer, idx++, maxlen); - format++; - break; - } - } - - // termination - out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen); - - // return written chars without terminating \0 - return (int)idx; -} - - -/////////////////////////////////////////////////////////////////////////////// - -int printf_(const char* format, ...) -{ - va_list va; - va_start(va, format); - char buffer[1]; - const int ret = _vsnprintf(out_putchar, buffer, (size_t)-1, format, va); - va_end(va); - return ret; -} - - -int sprintf_(char* buffer, const char* format, ...) -{ - va_list va; - va_start(va, format); - const int ret = _vsnprintf(out_buffer, buffer, (size_t)-1, format, va); - va_end(va); - return ret; -} - - -int snprintf_(char* buffer, size_t count, const char* format, ...) -{ - va_list va; - va_start(va, format); - const int ret = _vsnprintf(out_buffer, buffer, count, format, va); - va_end(va); - return ret; -} - - -int vprintf_(const char* format, va_list va) -{ - char buffer[1]; - return _vsnprintf(out_putchar, buffer, (size_t)-1, format, va); -} - -int vsprintf_(char* buffer, const char* format, va_list va) -{ - return _vsnprintf(out_buffer, buffer, (size_t)-1, format, va); -} - -int vsnprintf_(char* buffer, size_t count, const char* format, va_list va) -{ - return _vsnprintf(out_buffer, buffer, count, format, va); -} - - -int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...) -{ - va_list va; - va_start(va, format); - const int ret = vfctprintf(out, arg, format, va); - va_end(va); - return ret; -} - -int vfctprintf(void (*out)(char character, void* arg), void* arg, const char* format, va_list va) -{ - const out_function_wrapper_type out_fct_wrap = { out, arg }; - return _vsnprintf(out_wrapped_function, (char*)(uintptr_t)&out_fct_wrap, (size_t)-1, format, va); -} - diff --git a/src/kernel/printf/printf.h b/src/kernel/printf/printf.h deleted file mode 100644 index 84c83c9..0000000 --- a/src/kernel/printf/printf.h +++ /dev/null @@ -1,151 +0,0 @@ -/** - * @author (c) Eyal Rozenberg - * 2021, Haifa, Palestine/Israel - * @author (c) Marco Paland (info@paland.com) - * 2014-2019, PALANDesign Hannover, Germany - * - * @note Others have made smaller contributions to this file: see the - * contributors page at https://github.com/eyalroz/printf/graphs/contributors - * or ask one of the authors. - * - * @brief Small stand-alone implementation of the printf family of functions - * (`(v)printf`, `(v)s(n)printf` etc., geared towards use on embedded systems with - * a very limited resources. - * - * @note the implementations are thread-safe; re-entrant; use no functions from - * the standard library; and do not dynamically allocate any memory. - * - * @license The MIT License (MIT) - * - * 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. - */ - -#ifndef PRINTF_H_ -#define PRINTF_H_ - -#include -#include - - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef __GNUC__ -# define ATTR_PRINTF(one_based_format_index, first_arg) \ -__attribute__((format(__printf__, (one_based_format_index), (first_arg)))) -# define ATTR_VPRINTF(one_based_format_index) ATTR_PRINTF(one_based_format_index, 0) -#else -# define ATTR_PRINTF(one_based_format_index, first_arg) -# define ATTR_VPRINTF(one_based_format_index) -#endif - -#ifndef PRINTF_ALIAS_STANDARD_FUNCTION_NAMES -#define PRINTF_ALIAS_STANDARD_FUNCTION_NAMES 0 -#endif - -#if PRINTF_ALIAS_STANDARD_FUNCTION_NAMES -# define printf_ printf -# define sprintf_ sprintf -# define vsprintf_ vsprintf -# define snprintf_ snprintf -# define vsnprintf_ vsnprintf -# define vprintf_ vprintf -#endif - -/** - * Output a character to a custom device like UART, used by the printf() function - * This function is declared here only. You have to write your custom implementation somewhere - * @param character Character to output - */ -void putchar_(char character); - - -/** - * Tiny printf implementation - * You have to implement putchar_ if you use printf() - * To avoid conflicts with the regular printf() API it is overridden by macro defines - * and internal underscore-appended functions like printf_() are used - * @param format A string that specifies the format of the output - * @return The number of characters that are written into the array, not counting the terminating null character - */ -int printf_(const char* format, ...) ATTR_PRINTF(1, 2); - - -/** - * Tiny sprintf/vsprintf implementation - * Due to security reasons (buffer overflow) YOU SHOULD CONSIDER USING (V)SNPRINTF INSTEAD! - * @param buffer A pointer to the buffer where to store the formatted string. MUST be big enough to store the output! - * @param format A string that specifies the format of the output - * @param va A value identifying a variable arguments list - * @return The number of characters that are WRITTEN into the buffer, not counting the terminating null character - */ -int sprintf_(char* buffer, const char* format, ...) ATTR_PRINTF(2, 3); -int vsprintf_(char* buffer, const char* format, va_list va) ATTR_VPRINTF(2); - - -/** - * Tiny snprintf/vsnprintf implementation - * @param buffer A pointer to the buffer where to store the formatted string - * @param count The maximum number of characters to store in the buffer, including a terminating null character - * @param format A string that specifies the format of the output - * @param va A value identifying a variable arguments list - * @return The number of characters that COULD have been written into the buffer, not counting the terminating - * null character. A value equal or larger than count indicates truncation. Only when the returned value - * is non-negative and less than count, the string has been completely written. - */ -int snprintf_(char* buffer, size_t count, const char* format, ...) ATTR_PRINTF(3, 4); -int vsnprintf_(char* buffer, size_t count, const char* format, va_list va) ATTR_VPRINTF(3); - - -/** - * Tiny vprintf implementation - * @param format A string that specifies the format of the output - * @param va A value identifying a variable arguments list - * @return The number of characters that are WRITTEN into the buffer, not counting the terminating null character - */ -int vprintf_(const char* format, va_list va) ATTR_VPRINTF(1); - - -/** - * printf/vprintf with output function - * You may use this as dynamic alternative to printf() with its fixed _putchar() output - * @param out An output function which takes one character and an argument pointer - * @param arg An argument pointer for user data passed to output function - * @param format A string that specifies the format of the output - * @param va A value identifying a variable arguments list - * @return The number of characters that are sent to the output function, not counting the terminating null character - */ -int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...) ATTR_PRINTF(3, 4); -int vfctprintf(void (*out)(char character, void* arg), void* arg, const char* format, va_list va) ATTR_VPRINTF(3); - -#ifdef __cplusplus -} -#endif - -#if PRINTF_ALIAS_STANDARD_FUNCTION_NAMES -# undef printf_ -# undef sprintf_ -# undef vsprintf_ -# undef snprintf_ -# undef vsnprintf_ -# undef vprintf_ -#endif - -#endif // PRINTF_H_ diff --git a/src/kernel/printf/printf_config.h b/src/kernel/printf/printf_config.h deleted file mode 100644 index 972fff7..0000000 --- a/src/kernel/printf/printf_config.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -// \file printf_config.h -// Configuration for eyalroz/printf - -#define PRINTF_SUPPORT_DECIMAL_SPECIFIERS 0 -#define PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS 0 -#define PRINTF_SUPPORT_WRITEBACK_SPECIFIER 0 -#define PRINTF_SUPPORT_LONG_LONG 1 -#define PRINTF_ALIAS_STANDARD_FUNCTION_NAMES 1 diff --git a/src/libraries/util/format.cpp b/src/libraries/util/format.cpp index 3161a32..0a2f73c 100644 --- a/src/libraries/util/format.cpp +++ b/src/libraries/util/format.cpp @@ -5,22 +5,60 @@ namespace util { namespace { -const char digits[] = "0123456789abcdef"; +template struct char_traits; -template void -append_int(char *&out, size_t &count, size_t max, I value, unsigned min_width, char pad) +template <> +struct char_traits { - static constexpr size_t bufsize = sizeof(I)*3; + static constexpr const char digits[] = "0123456789abcdef"; + + static constexpr char d0 = '0'; + static constexpr char d9 = '9'; + + static constexpr char per = '%'; + static constexpr char space = ' '; + + static constexpr char d = 'd'; + static constexpr char l = 'l'; + static constexpr char s = 's'; + static constexpr char u = 'u'; + static constexpr char x = 'x'; +}; + +template <> +struct char_traits +{ + static constexpr const wchar_t digits[] = L"0123456789abcdef"; + + static constexpr wchar_t d0 = L'0'; + static constexpr wchar_t d9 = L'9'; + + static constexpr wchar_t per = L'%'; + static constexpr wchar_t space = L' '; + + static constexpr wchar_t d = L'd'; + static constexpr wchar_t l = L'l'; + static constexpr wchar_t s = L's'; + static constexpr wchar_t u = L'u'; + static constexpr wchar_t x = L'x'; +}; + + +template void +append_int(char_t *&out, size_t &count, size_t max, I value, unsigned min_width, char_t pad) +{ + using chars = char_traits; + static constexpr size_t bufsize = sizeof(I)*4; unsigned num_digits = 0; - char buffer[bufsize]; - char *p = buffer + (bufsize - 1); + char_t buffer[bufsize]; + char_t *p = buffer + (bufsize - 1); do { if (value) { - *p-- = digits[value % N]; + *p-- = chars::digits[value % N]; value /= N; } else { - *p-- = num_digits ? pad : '0'; + *p-- = num_digits ? pad : chars::d0; } num_digits++; } while (value || num_digits < min_width); @@ -33,81 +71,91 @@ append_int(char *&out, size_t &count, size_t max, I value, unsigned min_width, c } } -void -append_string(char *&out, size_t &count, size_t max, char const *value) +template void +append_string(char_t *&out, size_t &count, size_t max, unsigned width, char_t const *value) { + using chars = char_traits; + while (value && *value && count < max) { - count++; + ++count; + if (width) --width; *out++ = *value++; } + + while (width-- && count < max) { + ++count; + *out++ = chars::space; + } } } // namespace -size_t -vformat(stringbuf output, char const *format, va_list va) +template size_t +vformat(counted output, char_t const *format, va_list va) { - char * out = output.pointer; + using chars = char_traits; + + char_t * out = output.pointer; const size_t max = output.count - 1; size_t count = 0; while (format && *format && count < max) { - if (*format != '%') { + if (*format != chars::per) { count++; *out++ = *format++; continue; } format++; // chomp the % character - char spec = *format++; + char_t spec = *format++; bool long_type = false; - if (spec == '%') { + if (spec == chars::per) { count++; - *out++ = '%'; + *out++ = chars::per; continue; } unsigned width = 0; - char pad = ' '; + char_t pad = chars::space; while (spec) { bool done = false; - if (spec >= '0' && spec <= '9') { - if (spec == '0' && width == 0) - pad = '0'; + if (spec >= chars::d0 && spec <= chars::d9) { + if (spec == chars::d0 && width == 0) + pad = chars::d0; else - width = width * 10 + (spec - '0'); + width = width * 10 + (spec - chars::d0); spec = *format++; continue; } switch (spec) { - case 'l': + case chars::l: long_type = true; break; - case 'x': + case chars::x: if (long_type) - append_int(out, count, max, va_arg(va, uint64_t), width, pad); + append_int(out, count, max, va_arg(va, uint64_t), width, pad); else - append_int(out, count, max, va_arg(va, uint32_t), width, pad); + append_int(out, count, max, va_arg(va, uint32_t), width, pad); done = true; break; - case 'd': - case 'u': + case chars::d: + case chars::u: if (long_type) - append_int(out, count, max, va_arg(va, uint64_t), width, pad); + append_int(out, count, max, va_arg(va, uint64_t), width, pad); else - append_int(out, count, max, va_arg(va, uint32_t), width, pad); + append_int(out, count, max, va_arg(va, uint32_t), width, pad); done = true; break; - case 's': - append_string(out, count, max, va_arg(va, const char *)); + case chars::s: + append_string(out, count, max, width, va_arg(va, const char_t *)); done = true; break; } @@ -117,17 +165,24 @@ vformat(stringbuf output, char const *format, va_list va) } } + *out = 0; return count; } -size_t -format(stringbuf output, const char *format, ...) +template size_t +format(counted output, const char_t *format, ...) { va_list va; va_start(va, format); - size_t result = vformat(output, format, va); + size_t result = vformat(output, format, va); va_end(va); return result; } +template size_t format(counted output, const char *format, ...); +template size_t vformat(counted output, const char *format, va_list va); + +template size_t format(counted output, const wchar_t *format, ...); +template size_t vformat(counted output, const wchar_t *format, va_list va); + } //namespace util diff --git a/src/libraries/util/util/format.h b/src/libraries/util/util/format.h index 44ddde2..6051ee1 100644 --- a/src/libraries/util/util/format.h +++ b/src/libraries/util/util/format.h @@ -8,9 +8,10 @@ namespace util { -using stringbuf = counted; +template +size_t format(counted output, const char_t *format, ...); -size_t format(stringbuf output, const char *format, ...); -size_t vformat(stringbuf output, const char *format, va_list va); +template +size_t vformat(counted output, const char_t *format, va_list va); }