/**
 * @author (c) Eyal Rozenberg <eyalroz1@gmx.com>
 *             2021-2022, 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. The original code for exponential specifiers was
 * contributed by Martijn Jasperse <m.jasperse@gmail.com>.
 *
 * @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.
 */

// Define this globally (e.g. gcc -DPRINTF_INCLUDE_CONFIG_H=1 ...) to include the
// printf_config.h header file
#if PRINTF_INCLUDE_CONFIG_H
#include "printf_config.h"
#endif

#include <printf.h>

#ifdef __cplusplus
#include <cstdint>
#include <climits>
#else
#include <types.h>
#include <limits.h>
#endif // __cplusplus

#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

// size of the fixed (on-stack) buffer for printing individual decimal numbers.
// this must be big enough to hold one converted floating-point value including
// padded zeros.
#ifndef PRINTF_DECIMAL_BUFFER_SIZE
#define PRINTF_DECIMAL_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

// The number of terms in a Taylor series expansion of log_10(x) to
// use for approximation - including the power-zero term (i.e. the
// value at the point of expansion).
#ifndef PRINTF_LOG10_TAYLOR_TERMS
#define PRINTF_LOG10_TAYLOR_TERMS 4
#endif

#if PRINTF_LOG10_TAYLOR_TERMS <= 1
#error "At least one non-constant Taylor expansion is necessary for the log10() calculation"
#endif

// Be extra-safe, and don't assume format specifiers are completed correctly
// before the format string end.
#ifndef PRINTF_CHECK_FOR_NUL_IN_FORMAT_SPECIFIER
#define PRINTF_CHECK_FOR_NUL_IN_FORMAT_SPECIFIER 1
#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_INT (1U << 8U)
// Only used with PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS
#define FLAGS_LONG (1U << 9U)
#define FLAGS_LONG_LONG (1U << 10U)
#define FLAGS_PRECISION (1U << 11U)
#define FLAGS_ADAPT_EXP (1U << 12U)
#define FLAGS_POINTER (1U << 13U)
// Note: Similar, but not identical, effect as FLAGS_HASH
#define FLAGS_SIGNED (1U << 14U)
// Only used with PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS

#ifdef PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS

#define FLAGS_INT8 FLAGS_CHAR

#if (SHRT_MAX == 32767LL)
#define FLAGS_INT16 FLAGS_SHORT
#elif (INT_MAX == 32767LL)
#define FLAGS_INT16 FLAGS_INT
#elif (LONG_MAX == 32767LL)
#define FLAGS_INT16 FLAGS_LONG
#elif (LLONG_MAX == 32767LL)
#define FLAGS_INT16 FLAGS_LONG_LONG
#else
#error "No basic integer type has a size of 16 bits exactly"
#endif

#if (SHRT_MAX == 2147483647LL)
#define FLAGS_INT32 FLAGS_SHORT
#elif (INT_MAX == 2147483647LL)
#define FLAGS_INT32 FLAGS_INT
#elif (LONG_MAX == 2147483647LL)
#define FLAGS_INT32 FLAGS_LONG
#elif (LLONG_MAX == 2147483647LL)
#define FLAGS_INT32 FLAGS_LONG_LONG
#else
#error "No basic integer type has a size of 32 bits exactly"
#endif

#if (SHRT_MAX == 9223372036854775807LL)
#define FLAGS_INT64 FLAGS_SHORT
#elif (INT_MAX == 9223372036854775807LL)
#define FLAGS_INT64 FLAGS_INT
#elif (LONG_MAX == 9223372036854775807LL)
#define FLAGS_INT64 FLAGS_LONG
#elif (LLONG_MAX == 9223372036854775807LL)
#define FLAGS_INT64 FLAGS_LONG_LONG
#else
#error "No basic integer type has a size of 64 bits exactly"
#endif

#endif // PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS

typedef unsigned int printf_flags_t;

#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_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

// The printf()-family functions return an `int`; it is therefore
// unnecessary/inappropriate to use size_t - often larger than int
// in practice - for non-negative related values, such as widths,
// precisions, offsets into buffers used for printing and the sizes
// of these buffers. instead, we use:
typedef unsigned int printf_size_t;
#define PRINTF_MAX_POSSIBLE_BUFFER_SIZE INT_MAX
// If we were to nitpick, this would actually be INT_MAX + 1,
// since INT_MAX is the maximum return value, which excludes the
// trailing '\0'.

#if (PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS)
#include <float.h>
#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
#define DOUBLE_MAX_SUBNORMAL_EXPONENT_OF_10 -38
#define DOUBLE_MAX_SUBNORMAL_POWER_OF_10 1e-38

#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
#define DOUBLE_MAX_SUBNORMAL_EXPONENT_OF_10 -308
#define DOUBLE_MAX_SUBNORMAL_POWER_OF_10 1e-308

#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 __no_instrument_function double_with_bit_access get_bit_access(double x)
{
    double_with_bit_access dwba;
    dwba.F = x;
    return dwba;
}

static inline __no_instrument_function int get_sign_bit(double x)
{
    // The sign is stored in the highest bit
    return (int)(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)))

// wrapper (used as buffer) for output function type
//
// One of the following must hold:
// 1. max_chars is 0
// 2. buffer is non-null
// 3. function is non-null
//
// ... otherwise bad things will happen.
typedef struct
{
    void (*function)(char c, void *extra_arg);
    void *extra_function_arg;
    char *buffer;
    printf_size_t pos;
    printf_size_t max_chars;
} output_gadget_t;

// Note: This function currently assumes it is not passed a '\0' c,
// or alternatively, that '\0' can be passed to the function in the output
// gadget. The former assumption holds within the printf library. It also
// assumes that the output gadget has been properly initialized.
static inline __no_instrument_function void putchar_via_gadget(output_gadget_t *gadget, char c)
{
    printf_size_t write_pos = gadget->pos++;
    // We're _always_ increasing pos, so as to count how may characters
    // _would_ have been written if not for the max_chars limitation
    if (write_pos >= gadget->max_chars)
    {
        return;
    }
    if (gadget->function != NULL)
    {
        // No check for c == '\0' .
        gadget->function(c, gadget->extra_function_arg);
    }
    else
    {
        // it must be the case that gadget->buffer != NULL , due to the constraint
        // on output_gadget_t ; and note we're relying on write_pos being non-negative.
        gadget->buffer[write_pos] = c;
    }
}

// Possibly-write the string-terminating '\0' character
static inline __no_instrument_function void append_termination_with_gadget(output_gadget_t *gadget)
{
    if (gadget->function != NULL || gadget->max_chars == 0)
    {
        return;
    }
    if (gadget->buffer == NULL)
    {
        return;
    }
    printf_size_t null_char_pos = gadget->pos < gadget->max_chars ? gadget->pos : gadget->max_chars - 1;
    gadget->buffer[null_char_pos] = '\0';
}

// We can't use putchar_ as is, since our output gadget
// only takes pointers to functions with an extra argument
static inline __no_instrument_function void putchar_wrapper(char c, void *unused)
{
    putchar(c);
    UNUSED(unused);
}

static inline __no_instrument_function output_gadget_t discarding_gadget(void)
{
    output_gadget_t gadget;
    gadget.function = NULL;
    gadget.extra_function_arg = NULL;
    gadget.buffer = NULL;
    gadget.pos = 0;
    gadget.max_chars = 0;
    return gadget;
}

static inline __no_instrument_function output_gadget_t buffer_gadget(char *buffer, size_t buffer_size)
{
    printf_size_t usable_buffer_size = (buffer_size > PRINTF_MAX_POSSIBLE_BUFFER_SIZE) ? PRINTF_MAX_POSSIBLE_BUFFER_SIZE : (printf_size_t)buffer_size;
    output_gadget_t result = discarding_gadget();
    if (buffer != NULL)
    {
        result.buffer = buffer;
        result.max_chars = usable_buffer_size;
    }
    return result;
}

static inline __no_instrument_function output_gadget_t function_gadget(void (*function)(char, void *), void *extra_arg)
{
    output_gadget_t result = discarding_gadget();
    result.function = function;
    result.extra_function_arg = extra_arg;
    result.max_chars = PRINTF_MAX_POSSIBLE_BUFFER_SIZE;
    return result;
}

static inline __no_instrument_function output_gadget_t extern_putchar_gadget(void)
{
    return function_gadget(putchar_wrapper, NULL);
}

// internal secure strlen
// @return The length of the string (excluding the terminating 0) limited by 'maxsize'
// @note strlen uses size_t, but wes only use this function with printf_size_t
// variables - hence the signature.
static inline __no_instrument_function printf_size_t strnlen_s_(const char *str, printf_size_t maxsize)
{
    const char *s;
    for (s = str; *s && maxsize--; ++s)
        ;
    return (printf_size_t)(s - str);
}

// internal test if char is a digit (0-9)
// @return true if char is a digit
static inline __no_instrument_function bool is_digit_(char ch)
{
    return (ch >= '0') && (ch <= '9');
}

// internal ASCII string to printf_size_t conversion
static __no_instrument_function printf_size_t atou_(const char **str)
{
    printf_size_t i = 0U;
    while (is_digit_(**str))
    {
        i = i * 10U + (printf_size_t)(*((*str)++) - '0');
    }
    return i;
}

// output the specified string in reverse, taking care of any zero-padding
static __no_instrument_function void out_rev_(output_gadget_t *output, const char *buf, printf_size_t len, printf_size_t width, printf_flags_t flags)
{
    const printf_size_t start_pos = output->pos;

    // pad spaces up to given width
    if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD))
    {
        for (printf_size_t i = len; i < width; i++)
        {
            putchar_via_gadget(output, ' ');
        }
    }

    // reverse string
    while (len)
    {
        putchar_via_gadget(output, buf[--len]);
    }

    // append pad spaces up to given width
    if (flags & FLAGS_LEFT)
    {
        while (output->pos - start_pos < width)
        {
            putchar_via_gadget(output, ' ');
        }
    }
}

// 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 __no_instrument_function void print_integer_finalization(output_gadget_t *output, char *buf, printf_size_t len, bool negative, numeric_base_t base, printf_size_t precision, printf_size_t width, printf_flags_t flags)
{
    printf_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--; // This should suffice for BASE_OCTAL
            }
            if (len && (base == BASE_HEX || base == BASE_BINARY) && (unpadded_len < len))
            {
                len--; // ... and an extra one for 0x or 0b
            }
        }
        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++] = ' ';
        }
    }

    out_rev_(output, buf, len, width, flags);
}

// An internal itoa-like function
static __no_instrument_function void print_integer(output_gadget_t *output, printf_unsigned_value_t value, bool negative, numeric_base_t base, printf_size_t precision, printf_size_t width, printf_flags_t flags)
{
    char buf[PRINTF_INTEGER_BUFFER_SIZE];
    printf_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));
    }

    print_integer_finalization(output, buf, len, negative, base, precision, width, flags);
}

#if (PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS)

// Stores a fixed-precision representation of a double relative
// to a fixed precision (which cannot be determined by examining this structure)
struct double_components
{
    int_fast64_t integral;
    int_fast64_t fractional;
    // ... truncation of the actual fractional part of the double value, scaled
    // by the precision value
    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 __no_instrument_function double_components get_components(double number, printf_size_t precision)
{
    struct double_components number_;
    number_.is_negative = get_sign_bit(number);
    double abs_number = (number_.is_negative) ? -number : number;
    number_.integral = (int_fast64_t)abs_number;
    double remainder = (abs_number - (double)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) && ((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_;
}

#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS
struct scaling_factor
{
    double raw_factor;
    bool multiply; // if true, need to multiply by raw_factor; otherwise need to divide by it
};

static double apply_scaling(double num, struct scaling_factor normalization)
{
    return normalization.multiply ? num * normalization.raw_factor : num / normalization.raw_factor;
}

static double unapply_scaling(double normalized, struct scaling_factor normalization)
{
#ifdef __GNUC__
// accounting for a static analysis bug in GCC 6.x and earlier
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#endif
    return normalization.multiply ? normalized / normalization.raw_factor : normalized * normalization.raw_factor;
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
}

static 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;
}

static struct double_components get_normalized_components(bool negative, printf_size_t precision, double non_normalized, struct scaling_factor normalization, int floored_exp10)
{
    struct double_components components;
    components.is_negative = negative;
    double scaled = apply_scaling(non_normalized, normalization);

    bool close_to_representation_extremum = ((-floored_exp10 + (int)precision) >= DBL_MAX_10_EXP - 1);
    if (close_to_representation_extremum)
    {
        // We can't have a normalization factor which also accounts for the precision, i.e. moves
        // some decimal digits into the mantissa, since it's unrepresentable, or nearly unrepresentable.
        // So, we'll give up early on getting extra precision...
        return get_components(negative ? -scaled : scaled, precision);
    }
    components.integral = (int_fast64_t)scaled;
    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;

    components.fractional = (int_fast64_t)scaled_remainder; // when precision == 0, the assigned value should be 0
    scaled_remainder -= (double)components.fractional;      // when precision == 0, this will not change scaled_remainder

    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).
    // Note: for precision = 0, this will "translate" the rounding effect from
    // the fractional part to the integral part where it should actually be
    // felt (as prec_power_of_10 is 1)
    if ((double)components.fractional >= prec_power_of_10)
    {
        components.fractional = 0;
        ++components.integral;
    }
    return components;
}
#endif // PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS

static __no_instrument_function void print_broken_up_decimal(
    struct double_components number_, output_gadget_t *output, printf_size_t precision,
    printf_size_t width, printf_flags_t flags, char *buf, printf_size_t len)
{
    if (precision != 0U)
    {
        // do fractional part, as an unsigned number

        printf_size_t count = precision;

        // %g/%G mandates we skip the trailing 0 digits...
        if ((flags & FLAGS_ADAPT_EXP) && !(flags & FLAGS_HASH) && (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_DECIMAL_BUFFER_SIZE)
            {
                --count;
                buf[len++] = (char)('0' + number_.fractional % 10U);
                if (!(number_.fractional /= 10U))
                {
                    break;
                }
            }
            // add extra 0s
            while ((len < PRINTF_DECIMAL_BUFFER_SIZE) && (count > 0U))
            {
                buf[len++] = '0';
                --count;
            }
            if (len < PRINTF_DECIMAL_BUFFER_SIZE)
            {
                buf[len++] = '.';
            }
        }
    }
    else
    {
        if ((flags & FLAGS_HASH) && (len < PRINTF_DECIMAL_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_DECIMAL_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_DECIMAL_BUFFER_SIZE))
        {
            buf[len++] = '0';
        }
    }

    if (len < PRINTF_DECIMAL_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++] = ' ';
        }
    }

    out_rev_(output, buf, len, width, flags);
}

// internal ftoa for fixed decimal floating point
static __no_instrument_function void print_decimal_number(output_gadget_t *output, double number, printf_size_t precision, printf_size_t width, printf_flags_t flags, char *buf, printf_size_t len)
{
    struct double_components value_ = get_components(number, precision);
    print_broken_up_decimal(value_, output, precision, width, flags, buf, len);
}

#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS

// A floor function - but one which only works for numbers whose
// floor value is representable by an int.
static int bastardized_floor(double x)
{
    if (x >= 0)
    {
        return (int)x;
    }
    int n = (int)x;
    return (((double)n) == x) ? n : n - 1;
}

// Computes the base-10 logarithm of the input number - which must be an actual
// positive number (not infinity or NaN, nor a sub-normal)
static double log10_of_positive(double positive_number)
{
    // The implementation follows David Gay (https://www.ampl.com/netlib/fp/dtoa.c).
    //
    // Since log_10 ( M * 2^x ) = log_10(M) + x , we can separate the components of
    // our input number, and need only solve log_10(M) for M between 1 and 2 (as
    // the base-2 mantissa is always 1-point-something). In that limited range, a
    // Taylor series expansion of log10(x) should serve us well enough; and we'll
    // take the mid-point, 1.5, as the point of expansion.

    double_with_bit_access dwba = get_bit_access(positive_number);
    // based on the algorithm by David Gay (https://www.ampl.com/netlib/fp/dtoa.c)
    int exp2 = get_exp2(dwba);
    // drop the exponent, so dwba.F comes into the range [1,2)
    dwba.U = (dwba.U & (((double_uint_t)(1) << DOUBLE_STORED_MANTISSA_BITS) - 1U)) |
             ((double_uint_t)DOUBLE_BASE_EXPONENT << DOUBLE_STORED_MANTISSA_BITS);
    double z = (dwba.F - 1.5);
    return (
        // Taylor expansion around 1.5:
        0.1760912590556812420       // Expansion term 0: ln(1.5)            / ln(10)
        + z * 0.2895296546021678851 // Expansion term 1: (M - 1.5)   * 2/3  / ln(10)
#if PRINTF_LOG10_TAYLOR_TERMS > 2
        - z * z * 0.0965098848673892950 // Expansion term 2: (M - 1.5)^2 * 2/9  / ln(10)
#if PRINTF_LOG10_TAYLOR_TERMS > 3
        + z * z * z * 0.0428932821632841311 // Expansion term 2: (M - 1.5)^3 * 8/81 / ln(10)
#endif
#endif
        // exact log_2 of the exponent x, with logarithm base change
        + exp2 * 0.30102999566398119521 // = exp2 * log_10(2) = exp2 * ln(2)/ln(10)
    );
}

static double pow10_of_int(int floored_exp10)
{
    // A crude hack for avoiding undesired behavior with barely-normal or slightly-subnormal values.
    if (floored_exp10 == DOUBLE_MAX_SUBNORMAL_EXPONENT_OF_10)
    {
        return DOUBLE_MAX_SUBNORMAL_POWER_OF_10;
    }
    // Compute 10^(floored_exp10) but (try to) make sure that doesn't overflow
    double_with_bit_access dwba;
    int exp2 = bastardized_floor(floored_exp10 * 3.321928094887362 + 0.5);
    const double z = floored_exp10 * 2.302585092994046 - exp2 * 0.6931471805599453;
    const double z2 = z * z;
    dwba.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
    dwba.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14)))));
    return dwba.F;
}

static __no_instrument_function void print_exponential_number(output_gadget_t *output, double number, printf_size_t precision, printf_size_t width, printf_flags_t flags, char *buf, printf_size_t len)
{
    const bool negative = get_sign_bit(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 floored_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.
        floored_exp10 = 0; // ... and no need to set a normalization factor or check the powers table
    }
    else
    {
        double exp10 = log10_of_positive(abs_number);
        floored_exp10 = bastardized_floor(exp10);
        double p10 = pow10_of_int(floored_exp10);
        // correct for rounding errors
        if (abs_number < p10)
        {
            floored_exp10--;
            p10 /= 10;
        }
        abs_exp10_covered_by_powers_table = PRINTF_ABS(floored_exp10) < PRINTF_MAX_PRECOMPUTED_POWER_OF_10;
        normalization.raw_factor = abs_exp10_covered_by_powers_table ? powers_of_10[PRINTF_ABS(floored_exp10)] : p10;
    }

    // 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 = (floored_exp10 >= -4 && floored_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 - floored_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 = (floored_exp10 < 0 && abs_exp10_covered_by_powers_table);
    bool should_skip_normalization = (fall_back_to_decimal_only_mode || floored_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, floored_exp10);

    // 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) && floored_exp10 >= -1 && decimal_part_components.integral == powers_of_10[floored_exp10 + 1])
        {
            floored_exp10++; // Not strictly necessary, since floored_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)
        {
            floored_exp10++;
            decimal_part_components.integral = 1;
            decimal_part_components.fractional = 0;
        }
    }

    // the floored_exp10 format is "E%+03d" and largest possible floored_exp10 value for a 64-bit double
    // is "307" (for 2^1023), so we set aside 4-5 characters overall
    printf_size_t exp10_part_width = fall_back_to_decimal_only_mode ? 0U : (PRINTF_ABS(floored_exp10) < 100) ? 4U
                                                                                                             : 5U;

    printf_size_t 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 printf_size_t printed_exponential_start_pos = output->pos;
    print_broken_up_decimal(decimal_part_components, output, precision, decimal_part_width, flags, buf, len);

    if (!fall_back_to_decimal_only_mode)
    {
        putchar_via_gadget(output, (flags & FLAGS_UPPERCASE) ? 'E' : 'e');
        print_integer(output,
                      ABS_FOR_PRINTING(floored_exp10),
                      floored_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 (output->pos - printed_exponential_start_pos < width)
            {
                putchar_via_gadget(output, ' ');
            }
        }
    }
}
#endif // PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS

static __no_instrument_function void print_floating_point(output_gadget_t *output, double value, printf_size_t precision, printf_size_t width, printf_flags_t flags, bool prefer_exponential)
{
    char buf[PRINTF_DECIMAL_BUFFER_SIZE];
    printf_size_t len = 0U;

    // test for special values
    if (value != value)
    {
        out_rev_(output, "nan", 3, width, flags);
        return;
    }
    if (value < -DBL_MAX)
    {
        out_rev_(output, "fni-", 4, width, flags);
        return;
    }
    if (value > DBL_MAX)
    {
        out_rev_(output, (flags & FLAGS_PLUS) ? "fni+" : "fni", (flags & FLAGS_PLUS) ? 4U : 3U, width, flags);
        return;
    }

    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
        print_exponential_number(output, value, precision, width, flags, buf, len);
#endif
        return;
    }

    // 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_DECIMAL_BUFFER_SIZE) && (precision > PRINTF_MAX_SUPPORTED_PRECISION))
    {
        buf[len++] = '0'; // This respects the precision in terms of result length only
        precision--;
    }

#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS
    if (prefer_exponential)
        print_exponential_number(output, value, precision, width, flags, buf, len);
    else
#endif
        print_decimal_number(output, value, precision, width, flags, buf, len);
}

#endif // (PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS)

// Advances the format pointer past the flags, and returns the parsed flags
// due to the characters passed
static __no_instrument_function printf_flags_t parse_flags(const char **format)
{
    printf_flags_t flags = 0U;
    do
    {
        switch (**format)
        {
        case '0':
            flags |= FLAGS_ZEROPAD;
            (*format)++;
            break;
        case '-':
            flags |= FLAGS_LEFT;
            (*format)++;
            break;
        case '+':
            flags |= FLAGS_PLUS;
            (*format)++;
            break;
        case ' ':
            flags |= FLAGS_SPACE;
            (*format)++;
            break;
        case '#':
            flags |= FLAGS_HASH;
            (*format)++;
            break;
        default:
            return flags;
        }
    } while (true);
}

static inline __no_instrument_function void format_string_loop(output_gadget_t *output, const char *format, va_list args)
{
#if PRINTF_CHECK_FOR_NUL_IN_FORMAT_SPECIFIER
#define ADVANCE_IN_FORMAT_STRING(cptr_) \
    do                                  \
    {                                   \
        (cptr_)++;                      \
        if (!*(cptr_))                  \
            return;                     \
    } while (0)
#else
#define ADVANCE_IN_FORMAT_STRING(cptr_) (cptr_)++
#endif

    while (*format)
    {
        if (*format != '%')
        {
            // A regular content character
            putchar_via_gadget(output, *format);
            format++;
            continue;
        }
        // We're parsing a format specifier: %[flags][width][.precision][length]
        ADVANCE_IN_FORMAT_STRING(format);

        printf_flags_t flags = parse_flags(&format);

        // evaluate width field
        printf_size_t width = 0U;
        if (is_digit_(*format))
        {
            width = (printf_size_t)atou_(&format);
        }
        else if (*format == '*')
        {
            const int w = va_arg(args, int);
            if (w < 0)
            {
                flags |= FLAGS_LEFT; // reverse padding
                width = (printf_size_t)-w;
            }
            else
            {
                width = (printf_size_t)w;
            }
            ADVANCE_IN_FORMAT_STRING(format);
        }

        // evaluate precision field
        printf_size_t precision = 0U;
        if (*format == '.')
        {
            flags |= FLAGS_PRECISION;
            ADVANCE_IN_FORMAT_STRING(format);
            if (is_digit_(*format))
            {
                precision = (printf_size_t)atou_(&format);
            }
            else if (*format == '*')
            {
                const int precision_ = va_arg(args, int);
                precision = precision_ > 0 ? (printf_size_t)precision_ : 0U;
                ADVANCE_IN_FORMAT_STRING(format);
            }
        }

        // evaluate length field
        switch (*format)
        {
#ifdef PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS
        case 'I':
        {
            ADVANCE_IN_FORMAT_STRING(format);
            // Greedily parse for size in bits: 8, 16, 32 or 64
            switch (*format)
            {
            case '8':
                flags |= FLAGS_INT8;
                ADVANCE_IN_FORMAT_STRING(format);
                break;
            case '1':
                ADVANCE_IN_FORMAT_STRING(format);
                if (*format == '6')
                {
                    format++;
                    flags |= FLAGS_INT16;
                }
                break;
            case '3':
                ADVANCE_IN_FORMAT_STRING(format);
                if (*format == '2')
                {
                    ADVANCE_IN_FORMAT_STRING(format);
                    flags |= FLAGS_INT32;
                }
                break;
            case '6':
                ADVANCE_IN_FORMAT_STRING(format);
                if (*format == '4')
                {
                    ADVANCE_IN_FORMAT_STRING(format);
                    flags |= FLAGS_INT64;
                }
                break;
            default:
                break;
            }
            break;
        }
#endif
        case 'l':
            flags |= FLAGS_LONG;
            ADVANCE_IN_FORMAT_STRING(format);
            if (*format == 'l')
            {
                flags |= FLAGS_LONG_LONG;
                ADVANCE_IN_FORMAT_STRING(format);
            }
            break;
        case 'h':
            flags |= FLAGS_SHORT;
            ADVANCE_IN_FORMAT_STRING(format);
            if (*format == 'h')
            {
                flags |= FLAGS_CHAR;
                ADVANCE_IN_FORMAT_STRING(format);
            }
            break;
        case 't':
            flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
            ADVANCE_IN_FORMAT_STRING(format);
            break;
        case 'j':
            flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
            ADVANCE_IN_FORMAT_STRING(format);
            break;
        case 'z':
            flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
            ADVANCE_IN_FORMAT_STRING(format);
            break;
        default:
            break;
        }

        // evaluate specifier
        switch (*format)
        {
        case 'd':
        case 'i':
        case 'u':
        case 'x':
        case 'X':
        case 'o':
        case 'b':
        {

            if (*format == 'd' || *format == 'i')
            {
                flags |= FLAGS_SIGNED;
            }

            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; // decimal integers have no alternative presentation
            }

            if (*format == 'X')
            {
                flags |= FLAGS_UPPERCASE;
            }

            format++;
            // ignore '0' flag when precision is given
            if (flags & FLAGS_PRECISION)
            {
                flags &= ~FLAGS_ZEROPAD;
            }

            if (flags & FLAGS_SIGNED)
            {
                // A signed specifier: d, i or possibly I + bit size if enabled

                if (flags & FLAGS_LONG_LONG)
                {
#if PRINTF_SUPPORT_LONG_LONG
                    const long long value = va_arg(args, long long);
                    print_integer(output, ABS_FOR_PRINTING(value), value < 0, base, precision, width, flags);
#endif
                }
                else if (flags & FLAGS_LONG)
                {
                    const long value = va_arg(args, long);
                    print_integer(output, ABS_FOR_PRINTING(value), value < 0, base, precision, width, flags);
                }
                else
                {
                    // We never try to interpret the argument as something potentially-smaller than int,
                    // due to integer promotion rules: Even if the user passed a short int, short unsigned
                    // etc. - these will come in after promotion, as int's (or unsigned for the case of
                    // short unsigned when it has the same size as int)
                    const int value =
                        (flags & FLAGS_CHAR) ? (signed char)va_arg(args, int) : (flags & FLAGS_SHORT) ? (short int)va_arg(args, int)
                                                                                                      : va_arg(args, int);
                    print_integer(output, ABS_FOR_PRINTING(value), value < 0, base, precision, width, flags);
                }
            }
            else
            {
                // An unsigned specifier: u, x, X, o, b

                flags &= ~(FLAGS_PLUS | FLAGS_SPACE);

                if (flags & FLAGS_LONG_LONG)
                {
#if PRINTF_SUPPORT_LONG_LONG
                    print_integer(output, (printf_unsigned_value_t)va_arg(args, unsigned long long), false, base, precision, width, flags);
#endif
                }
                else if (flags & FLAGS_LONG)
                {
                    print_integer(output, (printf_unsigned_value_t)va_arg(args, unsigned long), false, base, precision, width, flags);
                }
                else
                {
                    const unsigned int value =
                        (flags & FLAGS_CHAR) ? (unsigned char)va_arg(args, unsigned int) : (flags & FLAGS_SHORT) ? (unsigned short int)va_arg(args, unsigned int)
                                                                                                                 : va_arg(args, unsigned int);
                    print_integer(output, (printf_unsigned_value_t)value, false, base, precision, width, flags);
                }
            }
            break;
        }
#if PRINTF_SUPPORT_DECIMAL_SPECIFIERS
        case 'f':
        case 'F':
            if (*format == 'F')
                flags |= FLAGS_UPPERCASE;
            print_floating_point(output, va_arg(args, 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;
            print_floating_point(output, va_arg(args, double), precision, width, flags, PRINTF_PREFER_EXPONENTIAL);
            format++;
            break;
#endif // PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS
        case 'c':
        {
            printf_size_t l = 1U;
            // pre padding
            if (!(flags & FLAGS_LEFT))
            {
                while (l++ < width)
                {
                    putchar_via_gadget(output, ' ');
                }
            }
            // char output
            putchar_via_gadget(output, (char)va_arg(args, int));
            // post padding
            if (flags & FLAGS_LEFT)
            {
                while (l++ < width)
                {
                    putchar_via_gadget(output, ' ');
                }
            }
            format++;
            break;
        }

        case 's':
        {
            const char *p = va_arg(args, char *);
            if (p == NULL)
            {
                out_rev_(output, ")llun(", 6, width, flags);
            }
            else
            {
                printf_size_t l = strnlen_s_(p, precision ? precision : PRINTF_MAX_POSSIBLE_BUFFER_SIZE);
                // pre padding
                if (flags & FLAGS_PRECISION)
                {
                    l = (l < precision ? l : precision);
                }
                if (!(flags & FLAGS_LEFT))
                {
                    while (l++ < width)
                    {
                        putchar_via_gadget(output, ' ');
                    }
                }
                // string output
                while ((*p != 0) && (!(flags & FLAGS_PRECISION) || precision))
                {
                    putchar_via_gadget(output, *(p++));
                    --precision;
                }
                // post padding
                if (flags & FLAGS_LEFT)
                {
                    while (l++ < width)
                    {
                        putchar_via_gadget(output, ' ');
                    }
                }
            }
            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(args, void *);
            (value == (uintptr_t)NULL) ? out_rev_(output, ")lin(", 5, width, flags) : print_integer(output, (printf_unsigned_value_t)value, false, BASE_HEX, precision, width, flags);
            format++;
            break;
        }

        case '%':
            putchar_via_gadget(output, '%');
            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(args, char *)) = (char)output->pos;
            else if (flags & FLAGS_SHORT)
                *(va_arg(args, short *)) = (short)output->pos;
            else if (flags & FLAGS_LONG)
                *(va_arg(args, long *)) = (long)output->pos;
#if PRINTF_SUPPORT_LONG_LONG
            else if (flags & FLAGS_LONG_LONG)
                *(va_arg(args, long long *)) = (long long int)output->pos;
#endif // PRINTF_SUPPORT_LONG_LONG
            else
                *(va_arg(args, int *)) = (int)output->pos;
            format++;
            break;
        }
#endif // PRINTF_SUPPORT_WRITEBACK_SPECIFIER

        default:
            putchar_via_gadget(output, *format);
            format++;
            break;
        }
    }
}

// internal vsnprintf - used for implementing _all library functions
static __no_instrument_function int vsnprintf_impl(output_gadget_t *output, const char *format, va_list args)
{
    // Note: The library only calls vsnprintf_impl() with output->pos being 0. However, it is
    // possible to call this function with a non-zero pos value for some "remedial printing".
    format_string_loop(output, format, args);

    // termination
    append_termination_with_gadget(output);

    // return written chars without terminating \0
    return (int)output->pos;
}

///////////////////////////////////////////////////////////////////////////////

__no_instrument_function int vprintf(const char *format, va_list arg)
{
    output_gadget_t gadget = extern_putchar_gadget();
    return vsnprintf_impl(&gadget, format, arg);
}

__no_instrument_function int vsnprintf(char *s, size_t n, const char *format, va_list arg)
{
    output_gadget_t gadget = buffer_gadget(s, n);
    return vsnprintf_impl(&gadget, format, arg);
}

__no_instrument_function int vsprintf(char *s, const char *format, va_list arg)
{
    return vsnprintf(s, PRINTF_MAX_POSSIBLE_BUFFER_SIZE, format, arg);
}

__no_instrument_function int vfctprintf(void (*out)(char c, void *extra_arg), void *extra_arg, const char *format, va_list arg)
{
    output_gadget_t gadget = function_gadget(out, extra_arg);
    return vsnprintf_impl(&gadget, format, arg);
}

__no_instrument_function int printf(const char *format, ...)
{
    va_list args;
    va_start(args, format);
    const int ret = vprintf(format, args);
    va_end(args);
    return ret;
}

__no_instrument_function int sprintf(char *s, const char *format, ...)
{
    va_list args;
    va_start(args, format);
    const int ret = vsprintf(s, format, args);
    va_end(args);
    return ret;
}

__no_instrument_function int snprintf(char *s, size_t n, const char *format, ...)
{
    va_list args;
    va_start(args, format);
    const int ret = vsnprintf(s, n, format, args);
    va_end(args);
    return ret;
}

__no_instrument_function int fctprintf(void (*out)(char c, void *extra_arg), void *extra_arg, const char *format, ...)
{
    va_list args;
    va_start(args, format);
    const int ret = vfctprintf(out, extra_arg, format, args);
    va_end(args);
    return ret;
}