/*
 * Copyright (c) 1997-2010, 2012-2015 Wind River Systems, Inc.
 * Copyright (c) 2020 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <ctype.h>
#include <errno.h>
#include <inttypes.h>
#include <limits.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <zephyr/toolchain.h>
#include <sys/types.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/cbprintf.h>

/* newlib doesn't declare this function unless __POSIX_VISIBLE >= 200809.  No
 * idea how to make that happen, so lets put it right here.
 */
size_t strnlen(const char *s, size_t maxlen);

/* Provide typedefs used for signed and unsigned integral types
 * capable of holding all convertible integral values.
 */
#ifdef CONFIG_CBPRINTF_FULL_INTEGRAL
typedef intmax_t sint_value_type;
typedef uintmax_t uint_value_type;
#else
typedef int32_t sint_value_type;
typedef uint32_t uint_value_type;
#endif

/* The maximum buffer size required is for octal formatting: one character for
 * every 3 bits.  Neither EOS nor alternate forms are required.
 */
#define CONVERTED_INT_BUFLEN ((CHAR_BIT * sizeof(uint_value_type) + 2) / 3)

/* The float code may extract up to 16 digits, plus a prefix, a
 * leading 0, a dot, and an exponent in the form e+xxx for a total of
 * 24. Add a trailing NULL so the buffer length required is 25.
 */
#define CONVERTED_FP_BUFLEN 25U

#ifdef CONFIG_CBPRINTF_FP_SUPPORT
#define CONVERTED_BUFLEN MAX(CONVERTED_INT_BUFLEN, CONVERTED_FP_BUFLEN)
#else
#define CONVERTED_BUFLEN CONVERTED_INT_BUFLEN
#endif

/* The allowed types of length modifier. */
enum length_mod_enum {
	LENGTH_NONE,		/* int */
	LENGTH_HH,		/* char */
	LENGTH_H,		/* short */
	LENGTH_L,		/* long */
	LENGTH_LL,		/* long long */
	LENGTH_J,		/* intmax */
	LENGTH_Z,		/* size_t */
	LENGTH_T,		/* ptrdiff_t */
	LENGTH_UPPER_L,		/* long double */
};

/* Categories of conversion specifiers. */
enum specifier_cat_enum {
	/* unrecognized */
	SPECIFIER_INVALID,
	/* d, i */
	SPECIFIER_SINT,
	/* c, o, u, x, X */
	SPECIFIER_UINT,
	/* n, p, s */
	SPECIFIER_PTR,
	/* a, A, e, E, f, F, g, G */
	SPECIFIER_FP,
};

#define CHAR_IS_SIGNED (CHAR_MIN != 0)
#if CHAR_IS_SIGNED
#define CASE_SINT_CHAR case 'c':
#define CASE_UINT_CHAR
#else
#define CASE_SINT_CHAR
#define CASE_UINT_CHAR case 'c':
#endif

/* We need two pieces of information about wchar_t:
 * * WCHAR_IS_SIGNED: whether it's signed or unsigned;
 * * WINT_TYPE: the type to use when extracting it from va_args
 *
 * The former can be determined from the value of WCHAR_MIN if it's defined.
 * It's not for minimal libc, so treat it as whatever char is.
 *
 * The latter should be wint_t, but minimal libc doesn't provide it.  We can
 * substitute wchar_t as long as that type does not undergo default integral
 * promotion as an argument.  But it does for at least one toolchain (xtensa),
 * and where it does we need to use the promoted type in va_arg() to avoid
 * build errors, otherwise we can use the base type.  We can tell that
 * integral promotion occurs if WCHAR_MAX is strictly less than INT_MAX.
 */
#ifndef WCHAR_MIN
#define WCHAR_IS_SIGNED CHAR_IS_SIGNED
#if WCHAR_IS_SIGNED
#define WINT_TYPE int
#else /* wchar signed */
#define WINT_TYPE unsigned int
#endif /* wchar signed */
#else /* WCHAR_MIN defined */
#define WCHAR_IS_SIGNED ((WCHAR_MIN - 0) != 0)
#if WCHAR_MAX < INT_MAX
/* Signed or unsigned, it'll be int */
#define WINT_TYPE int
#else /* wchar rank vs int */
#define WINT_TYPE wchar_t
#endif /* wchar rank vs int */
#endif /* WCHAR_MIN defined */

/* Case label to identify conversions for signed integral values.  The
 * corresponding argument_value tag is sint and category is
 * SPECIFIER_SINT.
 */
#define SINT_CONV_CASES				\
	'd':					\
	CASE_SINT_CHAR				\
	case 'i'

/* Case label to identify conversions for signed integral arguments.
 * The corresponding argument_value tag is uint and category is
 * SPECIFIER_UINT.
 */
#define UINT_CONV_CASES				\
	'o':					\
	CASE_UINT_CHAR				\
	case 'u':				\
	case 'x':				\
	case 'X'

/* Case label to identify conversions for floating point arguments.
 * The corresponding argument_value tag is either dbl or ldbl,
 * depending on length modifier, and the category is SPECIFIER_FP.
 */
#define FP_CONV_CASES				\
	'a':					\
	case 'A':				\
	case 'e':				\
	case 'E':				\
	case 'f':				\
	case 'F':				\
	case 'g':				\
	case 'G'

/* Case label to identify conversions for pointer arguments.  The
 * corresponding argument_value tag is ptr and the category is
 * SPECIFIER_PTR.
 */
#define PTR_CONV_CASES				\
	'n':					\
	case 'p':				\
	case 's'

/* Storage for an argument value. */
union argument_value {
	/* For SINT conversions */
	sint_value_type sint;

	/* For UINT conversions */
	uint_value_type uint;

	/* For FP conversions without L length */
	double dbl;

	/* For FP conversions with L length */
	long double ldbl;

	/* For PTR conversions */
	void *ptr;
};

/* Structure capturing all attributes of a conversion
 * specification.
 *
 * Initial values come from the specification, but are updated during
 * the conversion.
 */
struct conversion {
	/** Indicates flags are inconsistent */
	bool invalid: 1;

	/** Indicates flags are valid but not supported */
	bool unsupported: 1;

	/** Left-justify value in width */
	bool flag_dash: 1;

	/** Explicit sign */
	bool flag_plus: 1;

	/** Space for non-negative sign */
	bool flag_space: 1;

	/** Alternative form */
	bool flag_hash: 1;

	/** Pad with leading zeroes */
	bool flag_zero: 1;

	/** Width field present */
	bool width_present: 1;

	/** Width value from int argument
	 *
	 * width_value is set to the absolute value of the argument.
	 * If the argument is negative flag_dash is also set.
	 */
	bool width_star: 1;

	/** Precision field present */
	bool prec_present: 1;

	/** Precision from int argument
	 *
	 * prec_value is set to the value of a non-negative argument.
	 * If the argument is negative prec_present is cleared.
	 */
	bool prec_star: 1;

	/** Length modifier (value from length_mod_enum) */
	unsigned int length_mod: 4;

	/** Indicates an a or A conversion specifier.
	 *
	 * This affects how precision is handled.
	 */
	bool specifier_a: 1;

	/** Conversion specifier category (value from specifier_cat_enum) */
	unsigned int specifier_cat: 3;

	/** If set alternate form requires 0 before octal. */
	bool altform_0: 1;

	/** If set alternate form requires 0x before hex. */
	bool altform_0c: 1;

	/** Set when pad0_value zeroes are to be to be inserted after
	 * the decimal point in a floating point conversion.
	 */
	bool pad_postdp: 1;

	/** Set for floating point values that have a non-zero
	 * pad0_prefix or pad0_pre_exp.
	 */
	bool pad_fp: 1;

	/** Conversion specifier character */
	unsigned char specifier;

	union {
		/** Width value from specification.
		 *
		 * Valid until conversion begins.
		 */
		int width_value;

		/** Number of extra zeroes to be inserted around a
		 * formatted value:
		 *
		 * * before a formatted integer value due to precision
		 *   and flag_zero; or
		 * * before a floating point mantissa decimal point
		 *   due to precision; or
		 * * after a floating point mantissa decimal point due
		 *   to precision.
		 *
		 * For example for zero-padded hexadecimal integers
		 * this would insert where the angle brackets are in:
		 * 0x<>hhhh.
		 *
		 * For floating point numbers this would insert at
		 * either <1> or <2> depending on #pad_postdp:
		 * VVV<1>.<2>FFFFeEEE
		 *
		 * Valid after conversion begins.
		 */
		int pad0_value;
	};

	union {
		/** Precision from specification.
		 *
		 * Valid until conversion begins.
		 */
		int prec_value;

		/** Number of extra zeros to be inserted after a decimal
		 * point due to precision.
		 *
		 * Inserts at <> in: VVVV.FFFF<>eEE
		 *
		 * Valid after conversion begins.
		 */
		int pad0_pre_exp;
	};
};

/** Get a size represented as a sequence of decimal digits.
 *
 * @param[inout] str where to read from.  Updated to point to the first
 * unconsumed character.  There must be at least one non-digit character in
 * the referenced text.
 *
 * @return the decoded integer value.
 */
static size_t extract_decimal(const char **str)
{
	const char *sp = *str;
	size_t val = 0;

	while (isdigit((int)(unsigned char)*sp) != 0) {
		val = 10U * val + *sp++ - '0';
	}
	*str = sp;
	return val;
}

/** Extract C99 conversion specification flags.
 *
 * @param conv pointer to the conversion being defined.
 *
 * @param sp pointer to the first character after the % of a conversion
 * specifier.
 *
 * @return a pointer the first character that follows the flags.
 */
static inline const char *extract_flags(struct conversion *conv,
					const char *sp)
{
	bool loop = true;

	do {
		switch (*sp) {
		case '-':
			conv->flag_dash = true;
			break;
		case '+':
			conv->flag_plus = true;
			break;
		case ' ':
			conv->flag_space = true;
			break;
		case '#':
			conv->flag_hash = true;
			break;
		case '0':
			conv->flag_zero = true;
			break;
		default:
			loop = false;
		}
		if (loop) {
			++sp;
		}
	} while (loop);

	/* zero && dash => !zero */
	if (conv->flag_zero && conv->flag_dash) {
		conv->flag_zero = false;
	}

	/* space && plus => !plus, handled in emitter code */

	return sp;
}

/** Extract a C99 conversion specification width.
 *
 * @param conv pointer to the conversion being defined.
 *
 * @param sp pointer to the first character after the flags element of a
 * conversion specification.
 *
 * @return a pointer the first character that follows the width.
 */
static inline const char *extract_width(struct conversion *conv,
					const char *sp)
{
	conv->width_present = true;

	if (*sp == '*') {
		conv->width_star = true;
		return ++sp;
	}

	const char *wp = sp;
	size_t width = extract_decimal(&sp);

	if (sp != wp) {
		conv->width_present = true;
		conv->width_value = width;
		conv->unsupported |= ((conv->width_value < 0)
				      || (width != (size_t)conv->width_value));
	}

	return sp;
}

/** Extract a C99 conversion specification precision.
 *
 * @param conv pointer to the conversion being defined.
 *
 * @param sp pointer to the first character after the width element of a
 * conversion specification.
 *
 * @return a pointer the first character that follows the precision.
 */
static inline const char *extract_prec(struct conversion *conv,
				       const char *sp)
{
	conv->prec_present = (*sp == '.');

	if (!conv->prec_present) {
		return sp;
	}
	++sp;

	if (*sp == '*') {
		conv->prec_star = true;
		return ++sp;
	}

	size_t prec = extract_decimal(&sp);

	conv->prec_value = prec;
	conv->unsupported |= ((conv->prec_value < 0)
			      || (prec != (size_t)conv->prec_value));

	return sp;
}

/** Extract a C99 conversion specification length.
 *
 * @param conv pointer to the conversion being defined.
 *
 * @param sp pointer to the first character after the precision element of a
 * conversion specification.
 *
 * @return a pointer the first character that follows the precision.
 */
static inline const char *extract_length(struct conversion *conv,
					 const char *sp)
{
	switch (*sp) {
	case 'h':
		if (*++sp == 'h') {
			conv->length_mod = LENGTH_HH;
			++sp;
		} else {
			conv->length_mod = LENGTH_H;
		}
		break;
	case 'l':
		if (*++sp == 'l') {
			conv->length_mod = LENGTH_LL;
			++sp;
		} else {
			conv->length_mod = LENGTH_L;
		}
		break;
	case 'j':
		conv->length_mod = LENGTH_J;
		++sp;
		break;
	case 'z':
		conv->length_mod = LENGTH_Z;
		++sp;
		break;
	case 't':
		conv->length_mod = LENGTH_T;
		++sp;
		break;
	case 'L':
		conv->length_mod = LENGTH_UPPER_L;
		++sp;

		/* We recognize and consume these, but can't format
		 * them.
		 */
		conv->unsupported = true;
		break;
	default:
		conv->length_mod = LENGTH_NONE;
		break;
	}
	return sp;
}

/* Extract a C99 conversion specifier.
 *
 * This is the character that identifies the representation of the converted
 * value.
 *
 * @param conv pointer to the conversion being defined.
 *
 * @param sp pointer to the first character after the length element of a
 * conversion specification.
 *
 * @return a pointer the first character that follows the specifier.
 */
static inline const char *extract_specifier(struct conversion *conv,
					    const char *sp)
{
	bool unsupported = false;

	conv->specifier = *sp++;

	switch (conv->specifier) {
	case SINT_CONV_CASES:
		conv->specifier_cat = SPECIFIER_SINT;
		goto int_conv;
	case UINT_CONV_CASES:
		conv->specifier_cat = SPECIFIER_UINT;
int_conv:
		/* L length specifier not acceptable */
		if (conv->length_mod == LENGTH_UPPER_L) {
			conv->invalid = true;
		}

		/* For c LENGTH_NONE and LENGTH_L would be ok,
		 * but we don't support formatting wide characters.
		 */
		if (conv->specifier == 'c') {
			unsupported = (conv->length_mod != LENGTH_NONE);
		} else if (!IS_ENABLED(CONFIG_CBPRINTF_FULL_INTEGRAL)) {
			/* Disable conversion that might produce truncated
			 * results with buffers sized for 32 bits.
			 */
			switch (conv->length_mod) {
			case LENGTH_L:
				unsupported = sizeof(long) > 4;
				break;
			case LENGTH_LL:
				unsupported = sizeof(long long) > 4;
				break;
			case LENGTH_J:
				unsupported = sizeof(uintmax_t) > 4;
				break;
			case LENGTH_Z:
				unsupported = sizeof(size_t) > 4;
				break;
			case LENGTH_T:
				unsupported = sizeof(ptrdiff_t) > 4;
				break;
			default:
				/* Add an empty default with break, this is a defensive
				 * programming. Static analysis tool won't raise a violation
				 * if default is empty, but has that comment.
				 */
				break;
			}
		} else {
			;
		}
		break;

	case FP_CONV_CASES:
		conv->specifier_cat = SPECIFIER_FP;

		/* Don't support if disabled */
		if (!IS_ENABLED(CONFIG_CBPRINTF_FP_SUPPORT)) {
			unsupported = true;
			break;
		}

		/* When FP enabled %a support is still conditional. */
		conv->specifier_a = (conv->specifier == 'a')
			|| (conv->specifier == 'A');
		if (conv->specifier_a
		    && !IS_ENABLED(CONFIG_CBPRINTF_FP_A_SUPPORT)) {
			unsupported = true;
			break;
		}

		/* The l specifier has no effect.  Otherwise length
		 * modifiers other than L are invalid.
		 */
		if (conv->length_mod == LENGTH_L) {
			conv->length_mod = LENGTH_NONE;
		} else if ((conv->length_mod != LENGTH_NONE)
			   && (conv->length_mod != LENGTH_UPPER_L)) {
			conv->invalid = true;
		} else {
			;
		}

		break;

		/* PTR cases are distinct */
	case 'n':
		conv->specifier_cat = SPECIFIER_PTR;
		/* Anything except L */
		if (conv->length_mod == LENGTH_UPPER_L) {
			unsupported = true;
		}
		break;

	case 's':
	case 'p':
		conv->specifier_cat = SPECIFIER_PTR;

		/* p: only LENGTH_NONE
		 *
		 * s: LENGTH_NONE or LENGTH_L but wide
		 * characters not supported.
		 */
		if (conv->length_mod != LENGTH_NONE) {
			unsupported = true;
		}
		break;

	default:
		conv->invalid = true;
		break;
	}

	conv->unsupported |= unsupported;

	return sp;
}

/* Extract the complete C99 conversion specification.
 *
 * @param conv pointer to the conversion being defined.
 *
 * @param sp pointer to the % that introduces a conversion specification.
 *
 * @return pointer to the first character that follows the specification.
 */
static inline const char *extract_conversion(struct conversion *conv,
					     const char *sp)
{
	*conv = (struct conversion) {
	   .invalid = false,
	};

	/* Skip over the opening %.  If the conversion specifier is %,
	 * that's the only thing that should be there, so
	 * fast-exit.
	 */
	++sp;
	if (*sp == '%') {
		conv->specifier = *sp++;
		return sp;
	}

	sp = extract_flags(conv, sp);
	sp = extract_width(conv, sp);
	sp = extract_prec(conv, sp);
	sp = extract_length(conv, sp);
	sp = extract_specifier(conv, sp);

	return sp;
}

#ifdef CONFIG_64BIT

static void _ldiv5(uint64_t *v)
{
	/* The compiler can optimize this on its own on 64-bit architectures */
	*v /= 5U;
}

#else /* CONFIG_64BIT */

/*
 * Tiny integer divide-by-five routine.  The full 64 bit division
 * implementations in libgcc are very large on some architectures, and
 * currently nothing in Zephyr pulls it into the link.  So it makes
 * sense to define this much smaller special case here to avoid
 * including it just for printf.
 *
 * It works by multiplying v by the reciprocal of 5 i.e.:
 *
 *	result = v * ((1 << 64) / 5) / (1 << 64)
 *
 * This produces a 128-bit result, but we drop the bottom 64 bits which
 * accounts for the division by (1 << 64). The product is kept to 64 bits
 * by summing partial multiplications and shifting right by 32 which on
 * most 32-bit architectures means only a register drop.
 *
 * Here the multiplier is: (1 << 64) / 5 = 0x3333333333333333
 * i.e. a 62 bits value. To compensate for the reduced precision, we
 * add an initial bias of 1 to v. This conveniently allows for keeping
 * the multiplier in a single 32-bit register given its pattern.
 * Enlarging the multiplier to 64 bits would also work but carry handling
 * on the summing of partial mults would be necessary, and a final right
 * shift would be needed, requiring more instructions.
 */
static void _ldiv5(uint64_t *v)
{
	uint32_t v_lo = *v;
	uint32_t v_hi = *v >> 32;
	uint32_t m = 0x33333333;
	uint64_t result;

	/*
	 * Force the multiplier constant into a register and make it
	 * opaque to the compiler, otherwise gcc tries to be too smart
	 * for its own good with a large expansion of adds and shifts.
	 */
	__asm__ ("" : "+r" (m));

	/*
	 * Apply a bias of 1 to v. We can't add it to v as this would overflow
	 * it when at max range. Factor it out with the multiplier upfront.
	 */
	result = ((uint64_t)m << 32) | m;

	/* The actual multiplication. */
	result += (uint64_t)v_lo * m;
	result >>= 32;
	result += (uint64_t)v_lo * m;
	result += (uint64_t)v_hi * m;
	result >>= 32;
	result += (uint64_t)v_hi * m;

	*v = result;
}

#endif /* CONFIG_64BIT */

/* Division by 10 */
static void _ldiv10(uint64_t *v)
{
	*v >>= 1;
	_ldiv5(v);
}

/* Extract the next decimal character in the converted representation of a
 * fractional component.
 */
static char _get_digit(uint64_t *fr, int *digit_count)
{
	char rval;

	if (*digit_count > 0) {
		--*digit_count;
		*fr *= 10U;
		rval = ((*fr >> 60) & 0xF) + '0';
		*fr &= (BIT64(60) - 1U);
	} else {
		rval = '0';
	}

	return rval;
}

static inline size_t conversion_radix(char specifier)
{
	switch (specifier) {
	default:
	case 'd':
	case 'i':
	case 'u':
		return 10;
	case 'o':
		return 8;
	case 'p':
	case 'x':
	case 'X':
		return 16;
	}
}

/* Writes the given value into the buffer in the specified base.
 *
 * Precision is applied *ONLY* within the space allowed.
 *
 * Alternate form value is applied to o, x, and X conversions.
 *
 * The buffer is filled backwards, so the input bpe is the end of the
 * generated representation.  The returned pointer is to the first
 * character of the representation.
 */
static char *encode_uint(uint_value_type value,
			 struct conversion *conv,
			 char *bps,
			 const char *bpe)
{
	bool upcase = isupper((int)conv->specifier) != 0;
	const unsigned int radix = conversion_radix(conv->specifier);
	char *bp = bps + (bpe - bps);

	do {
		unsigned int lsv = (unsigned int)(value % radix);

		*--bp = (lsv <= 9) ? ('0' + lsv)
			: upcase ? ('A' + lsv - 10) : ('a' + lsv - 10);
		value /= radix;
	} while ((value != 0) && (bps < bp));

	/* Record required alternate forms.  This can be determined
	 * from the radix without re-checking specifier.
	 */
	if (conv->flag_hash) {
		if (radix == 8) {
			conv->altform_0 = true;
		} else if (radix == 16) {
			conv->altform_0c = true;
		} else {
			;
		}
	}

	return bp;
}

/* Number of bits in the fractional part of an IEEE 754-2008 double
 * precision float.
 */
#define FRACTION_BITS 52

/* Number of hex "digits" in the fractional part of an IEEE 754-2008
 * double precision float.
 */
#define FRACTION_HEX DIV_ROUND_UP(FRACTION_BITS, 4)

/* Number of bits in the exponent of an IEEE 754-2008 double precision
 * float.
 */
#define EXPONENT_BITS 11

/* Mask for the sign (negative) bit of an IEEE 754-2008 double precision
 * float.
 */
#define SIGN_MASK BIT64(63)

/* Mask for the high-bit of a uint64_t representation of a fractional
 * value.
 */
#define BIT_63 BIT64(63)

/* Convert the IEEE 754-2008 double to text format.
 *
 * @param value the 64-bit floating point value.
 *
 * @param conv details about how the conversion is to proceed.  Some fields
 * are adjusted based on the value being converted.
 *
 * @param precision the precision for the conversion (generally digits past
 * the decimal point).
 *
 * @param bps pointer to the first character in a buffer that will hold the
 * converted value.
 *
 * @param bpe On entry this points to the end of the buffer reserved to hold
 * the converted value.  On exit it is updated to point just past the
 * converted value.
 *
 * return a pointer to the start of the converted value.  This may not be @p
 * bps but will be consistent with the exit value of *bpe.
 */
static char *encode_float(double value,
			  struct conversion *conv,
			  int precision,
			  char *sign,
			  char *bps,
			  const char **bpe)
{
	union {
		uint64_t u64;
		double dbl;
	} u = {
		.dbl = value,
	};
	bool prune_zero = false;
	char *buf = bps;

	/* Prepend the sign: '-' if negative, flags control
	 * non-negative behavior.
	 */
	if ((u.u64 & SIGN_MASK) != 0U) {
		*sign = '-';
	} else if (conv->flag_plus) {
		*sign = '+';
	} else if (conv->flag_space) {
		*sign = ' ';
	} else {
		;
	}

	/* Extract the non-negative offset exponent and fraction.  Record
	 * whether the value is subnormal.
	 */
	char c = conv->specifier;
	int expo = (u.u64 >> FRACTION_BITS) & BIT_MASK(EXPONENT_BITS);
	uint64_t fract = u.u64 & BIT64_MASK(FRACTION_BITS);
	bool is_subnormal = (expo == 0) && (fract != 0);

	/* Exponent of all-ones signals infinity or NaN, which are
	 * text constants regardless of specifier.
	 */
	if (expo == BIT_MASK(EXPONENT_BITS)) {
		if (fract == 0) {
			if (isupper((unsigned char)c) != 0) {
				*buf++ = 'I';
				*buf++ = 'N';
				*buf++ = 'F';
			} else {
				*buf++ = 'i';
				*buf++ = 'n';
				*buf++ = 'f';
			}
		} else {
			if (isupper((unsigned char)c) != 0) {
				*buf++ = 'N';
				*buf++ = 'A';
				*buf++ = 'N';
			} else {
				*buf++ = 'n';
				*buf++ = 'a';
				*buf++ = 'n';
			}
		}

		/* No zero-padding with text values */
		conv->flag_zero = false;

		*bpe = buf;
		return bps;
	}

	/* The case of an F specifier is no longer relevant. */
	if (c == 'F') {
		c = 'f';
	}

	/* Handle converting to the hex representation. */
	if (IS_ENABLED(CONFIG_CBPRINTF_FP_A_SUPPORT)
	    && (IS_ENABLED(CONFIG_CBPRINTF_FP_ALWAYS_A)
		|| conv->specifier_a)) {
		*buf++ = '0';
		*buf++ = 'x';

		/* Remove the offset from the exponent, and store the
		 * non-fractional value.  Subnormals require increasing the
		 * exponent as first bit isn't the implicit bit.
		 */
		expo -= 1023;
		if (is_subnormal) {
			*buf++ = '0';
			++expo;
		} else {
			*buf++ = '1';
		}

		/* If we didn't get precision from a %a specification then we
		 * treat it as from a %a specification with no precision: full
		 * range, zero-pruning enabled.
		 *
		 * Otherwise we have to cap the precision of the generated
		 * fraction, or possibly round it.
		 */
		if (!(conv->specifier_a && conv->prec_present)) {
			precision = FRACTION_HEX;
			prune_zero = true;
		} else if (precision > FRACTION_HEX) {
			conv->pad0_pre_exp = precision - FRACTION_HEX;
			conv->pad_fp = true;
			precision = FRACTION_HEX;
		} else if ((fract != 0)
			   && (precision < FRACTION_HEX)) {
			size_t pos = 4 * (FRACTION_HEX - precision) - 1;
			uint64_t mask = BIT64(pos);

			/* Round only if the bit that would round is
			 * set.
			 */
			if (fract & mask) {
				fract += mask;
			}
		}

		/* Record whether we must retain the decimal point even if we
		 * can prune zeros.
		 */
		bool require_dp = ((fract != 0) || conv->flag_hash);

		if (require_dp || (precision != 0)) {
			*buf++ = '.';
		}

		/* Get the fractional value as a hexadecimal string, using x
		 * for a and X for A.
		 */
		struct conversion aconv = {
			.specifier = isupper((unsigned char)c) != 0 ? 'X' : 'x',
		};
		const char *spe = *bpe;
		char *sp = bps + (spe - bps);

		if (fract != 0) {
			sp = encode_uint(fract, &aconv, buf, spe);
		}

		/* Pad out to full range since this is below the decimal
		 * point.
		 */
		while ((spe - sp) < FRACTION_HEX) {
			*--sp = '0';
		}

		/* Append the leading significant "digits". */
		while ((sp < spe) && (precision > 0)) {
			*buf++ = *sp++;
			--precision;
		}

		if (prune_zero) {
			while (*--buf == '0') {
				;
			}
			if ((*buf != '.') || require_dp) {
				++buf;
			}
		}

		*buf++ = 'p';
		if (expo >= 0) {
			*buf++ = '+';
		} else {
			*buf++ = '-';
			expo = -expo;
		}

		aconv.specifier = 'i';
		sp = encode_uint(expo, &aconv, buf, spe);

		while (sp < spe) {
			*buf++ = *sp++;
		}

		*bpe = buf;
		return bps;
	}

	/* Remainder of code operates on a 64-bit fraction, so shift up (and
	 * discard garbage from the exponent where the implicit 1 would be
	 * stored).
	 */
	fract <<= EXPONENT_BITS;
	fract &= ~SIGN_MASK;

	/* Non-zero values need normalization. */
	if ((expo | fract) != 0) {
		if (is_subnormal) {
			/* Fraction is subnormal.  Normalize it and correct
			 * the exponent.
			 */
			while (((fract <<= 1) & BIT_63) == 0) {
				expo--;
			}
		}
		/* Adjust the offset exponent to be signed rather than offset,
		 * and set the implicit 1 bit in the (shifted) 53-bit
		 * fraction.
		 */
		expo -= (1023 - 1);	/* +1 since .1 vs 1. */
		fract |= BIT_63;
	}

	/*
	 * Let's consider:
	 *
	 *	value = fract * 2^expo * 10^decexp
	 *
	 * Initially decexp = 0. The goal is to bring exp between
	 * 0 and -2 as the magnitude of a fractional decimal digit is 3 bits.
	 */
	int decexp = 0;

	while (expo < -2) {
		/*
		 * Make room to allow a multiplication by 5 without overflow.
		 * We test only the top part for faster code.
		 */
		do {
			fract >>= 1;
			expo++;
		} while ((uint32_t)(fract >> 32) >= (UINT32_MAX / 5U));

		/* Perform fract * 5 * 2 / 10 */
		fract *= 5U;
		expo++;
		decexp--;
	}

	while (expo > 0) {
		/*
		 * Perform fract / 5 / 2 * 10.
		 * The +2 is there to do round the result of the division
		 * by 5 not to lose too much precision in extreme cases.
		 */
		fract += 2;
		_ldiv5(&fract);
		expo--;
		decexp++;

		/* Bring back our fractional number to full scale */
		do {
			fract <<= 1;
			expo--;
		} while (!(fract & BIT_63));
	}

	/*
	 * The binary fractional point is located somewhere above bit 63.
	 * Move it between bits 59 and 60 to give 4 bits of room to the
	 * integer part.
	 */
	fract >>= (4 - expo);

	if ((c == 'g') || (c == 'G')) {
		/* Use the specified precision and exponent to select the
		 * representation and correct the precision and zero-pruning
		 * in accordance with the ISO C rule.
		 */
		if (decexp < (-4 + 1) || decexp > precision) {
			c += 'e' - 'g';  /* e or E */
			if (precision > 0) {
				precision--;
			}
		} else {
			c = 'f';
			precision -= decexp;
		}
		if (!conv->flag_hash && (precision > 0)) {
			prune_zero = true;
		}
	}

	int decimals;
	if (c == 'f') {
		decimals = precision + decexp;
		if (decimals < 0) {
			decimals = 0;
		}
	} else {
		decimals = precision + 1;
	}

	int digit_count = 16;

	if (decimals > 16) {
		decimals = 16;
	}

	/* Round the value to the last digit being printed. */
	uint64_t round = BIT64(59); /* 0.5 */
	while (decimals--) {
		_ldiv10(&round);
	}
	fract += round;
	/* Make sure rounding didn't make fract >= 1.0 */
	if (fract >= BIT64(60)) {
		_ldiv10(&fract);
		decexp++;
	}

	if (c == 'f') {
		if (decexp > 0) {
			/* Emit the digits above the decimal point. */
			while (decexp > 0 && digit_count > 0) {
				*buf++ = _get_digit(&fract, &digit_count);
				decexp--;
			}

			conv->pad0_value = decexp;

			decexp = 0;
		} else {
			*buf++ = '0';
		}

		/* Emit the decimal point only if required by the alternative
		 * format, or if more digits are to follow.
		 */
		if (conv->flag_hash || (precision > 0)) {
			*buf++ = '.';
		}

		if (decexp < 0 && precision > 0) {
			conv->pad0_value = -decexp;
			if (conv->pad0_value > precision) {
				conv->pad0_value = precision;
			}

			precision -= conv->pad0_value;
			conv->pad_postdp = (conv->pad0_value > 0);
		}
	} else { /* e or E */
		/* Emit the one digit before the decimal.  If it's not zero,
		 * this is significant so reduce the base-10 exponent.
		 */
		*buf = _get_digit(&fract, &digit_count);
		if (*buf++ != '0') {
			decexp--;
		}

		/* Emit the decimal point only if required by the alternative
		 * format, or if more digits are to follow.
		 */
		if (conv->flag_hash || (precision > 0)) {
			*buf++ = '.';
		}
	}

	while (precision > 0 && digit_count > 0) {
		*buf++ = _get_digit(&fract, &digit_count);
		precision--;
	}

	conv->pad0_pre_exp = precision;

	if (prune_zero) {
		conv->pad0_pre_exp = 0;
		while (*--buf == '0') {
			;
		}
		if (*buf != '.') {
			buf++;
		}
	}

	/* Emit the explicit exponent, if format requires it. */
	if ((c == 'e') || (c == 'E')) {
		*buf++ = c;
		if (decexp < 0) {
			decexp = -decexp;
			*buf++ = '-';
		} else {
			*buf++ = '+';
		}

		/* At most 3 digits to the decimal.  Spit them out. */
		if (decexp >= 100) {
			*buf++ = (decexp / 100) + '0';
			decexp %= 100;
		}

		*buf++ = (decexp / 10) + '0';
		*buf++ = (decexp % 10) + '0';
	}

	/* Cache whether there's padding required */
	conv->pad_fp = (conv->pad0_value > 0)
		|| (conv->pad0_pre_exp > 0);

	/* Set the end of the encoded sequence, and return its start.  Also
	 * store EOS as a non-digit/non-decimal value so we don't have to
	 * check against bpe when iterating in multiple places.
	 */
	*bpe = buf;
	*buf = 0;
	return bps;
}

/* Store a count into the pointer provided in a %n specifier.
 *
 * @param conv the specifier that indicates the size of the value into which
 * the count will be stored.
 *
 * @param dp where the count should be stored.
 *
 * @param count the count to be stored.
 */
static inline void store_count(const struct conversion *conv,
			       void *dp,
			       int count)
{
	switch ((enum length_mod_enum)conv->length_mod) {
	case LENGTH_NONE:
		*(int *)dp = count;
		break;
	case LENGTH_HH:
		*(signed char *)dp = (signed char)count;
		break;
	case LENGTH_H:
		*(short *)dp = (short)count;
		break;
	case LENGTH_L:
		*(long *)dp = (long)count;
		break;
	case LENGTH_LL:
		*(long long *)dp = (long long)count;
		break;
	case LENGTH_J:
		*(intmax_t *)dp = (intmax_t)count;
		break;
	case LENGTH_Z:
		*(size_t *)dp = (size_t)count;
		break;
	case LENGTH_T:
		*(ptrdiff_t *)dp = (ptrdiff_t)count;
		break;
	default:
		/* Add an empty default with break, this is a defensive programming.
		 * Static analysis tool won't raise a violation if default is empty,
		 * but has that comment.
		 */
		break;
	}
}

/* Outline function to emit all characters in [sp, ep). */
static int outs(cbprintf_cb out,
		void *ctx,
		const char *sp,
		const char *ep)
{
	size_t count = 0;

	while ((sp < ep) || ((ep == NULL) && *sp)) {
		int rc = out((int)*sp++, ctx);

		if (rc < 0) {
			return rc;
		}
		++count;
	}

	return (int)count;
}

int z_cbvprintf_impl(cbprintf_cb out, void *ctx, const char *fp,
		     va_list ap, uint32_t flags)
{
	char buf[CONVERTED_BUFLEN];
	size_t count = 0;
	sint_value_type sint;

	const bool tagged_ap = (flags & Z_CBVPRINTF_PROCESS_FLAG_TAGGED_ARGS)
			       == Z_CBVPRINTF_PROCESS_FLAG_TAGGED_ARGS;

/* Output character, returning EOF if output failed, otherwise
 * updating count.
 *
 * NB: c is evaluated exactly once: side-effects are OK
 */
#define OUTC(c) do { \
	int rc = (*out)((int)(c), ctx); \
	\
	if (rc < 0) { \
		return rc; \
	} \
	++count; \
} while (false)

/* Output sequence of characters, returning a negative error if output
 * failed.
 */

#define OUTS(_sp, _ep) do { \
	int rc = outs(out, ctx, _sp, _ep); \
	\
	if (rc < 0) {	    \
		return rc; \
	} \
	count += rc; \
} while (false)

	while (*fp != 0) {
		if (*fp != '%') {
			OUTC(*fp++);
			continue;
		}

		/* Force union into RAM with conversion state to
		 * mitigate LLVM code generation bug.
		 */
		struct {
			union argument_value value;
			struct conversion conv;
		} state = {
			.value = {
				.uint = 0,
			},
		};
		struct conversion *const conv = &state.conv;
		union argument_value *const value = &state.value;
		const char *sp = fp;
		int width = -1;
		int precision = -1;
		const char *bps = NULL;
		const char *bpe = buf + sizeof(buf);
		char sign = 0;

		fp = extract_conversion(conv, sp);

		if (conv->specifier_cat != SPECIFIER_INVALID) {
			if (IS_ENABLED(CONFIG_CBPRINTF_PACKAGE_SUPPORT_TAGGED_ARGUMENTS)
			    && tagged_ap) {
				/* Skip over the argument tag as it is not being
				 * used here.
				 */
				(void)va_arg(ap, int);
			}
		}

		/* If dynamic width is specified, process it,
		 * otherwise set width if present.
		 */
		if (conv->width_star) {
			width = va_arg(ap, int);

			if (width < 0) {
				conv->flag_dash = true;
				width = -width;
			}
		} else if (conv->width_present) {
			width = conv->width_value;
		} else {
			;
		}

		/* If dynamic precision is specified, process it, otherwise
		 * set precision if present.  For floating point where
		 * precision is not present use 6.
		 */
		if (conv->prec_star) {
			int arg = va_arg(ap, int);

			if (arg < 0) {
				conv->prec_present = false;
			} else {
				precision = arg;
			}
		} else if (conv->prec_present) {
			precision = conv->prec_value;
		} else {
			;
		}

		/* Reuse width and precision memory in conv for value
		 * padding counts.
		 */
		conv->pad0_value = 0;
		conv->pad0_pre_exp = 0;

		/* FP conversion requires knowing the precision. */
		if (IS_ENABLED(CONFIG_CBPRINTF_FP_SUPPORT)
		    && (conv->specifier_cat == SPECIFIER_FP)
		    && !conv->prec_present) {
			if (conv->specifier_a) {
				precision = FRACTION_HEX;
			} else {
				precision = 6;
			}
		}

		/* Get the value to be converted from the args.
		 *
		 * This can't be extracted to a helper function because
		 * passing a pointer to va_list doesn't work on x86_64.  See
		 * https://stackoverflow.com/a/8048892.
		 */
		enum specifier_cat_enum specifier_cat
			= (enum specifier_cat_enum)conv->specifier_cat;
		enum length_mod_enum length_mod
			= (enum length_mod_enum)conv->length_mod;

		/* Extract the value based on the argument category and length.
		 *
		 * Note that the length modifier doesn't affect the value of a
		 * pointer argument.
		 */
		if (specifier_cat == SPECIFIER_SINT) {
			switch (length_mod) {
			default:
			case LENGTH_NONE:
			case LENGTH_HH:
			case LENGTH_H:
				value->sint = va_arg(ap, int);
				break;
			case LENGTH_L:
				if (WCHAR_IS_SIGNED
				    && (conv->specifier == 'c')) {
					value->sint = (wchar_t)va_arg(ap,
							      WINT_TYPE);
				} else {
					value->sint = va_arg(ap, long);
				}
				break;
			case LENGTH_LL:
				value->sint =
					(sint_value_type)va_arg(ap, long long);
				break;
			case LENGTH_J:
				value->sint =
					(sint_value_type)va_arg(ap, intmax_t);
				break;
			case LENGTH_Z:		/* size_t */
			case LENGTH_T:		/* ptrdiff_t */
				/* Though ssize_t is the signed equivalent of
				 * size_t for POSIX, there is no uptrdiff_t.
				 * Assume that size_t and ptrdiff_t are the
				 * unsigned and signed equivalents of each
				 * other.  This can be checked in a platform
				 * test.
				 */
				value->sint =
					(sint_value_type)va_arg(ap, ptrdiff_t);
				break;
			}
			if (length_mod == LENGTH_HH) {
				value->sint = (signed char)value->sint;
			} else if (length_mod == LENGTH_H) {
				value->sint = (short)value->sint;
			}
		} else if (specifier_cat == SPECIFIER_UINT) {
			switch (length_mod) {
			default:
			case LENGTH_NONE:
			case LENGTH_HH:
			case LENGTH_H:
				value->uint = va_arg(ap, unsigned int);
				break;
			case LENGTH_L:
				if ((!WCHAR_IS_SIGNED)
				    && (conv->specifier == 'c')) {
					value->uint = (wchar_t)va_arg(ap,
							      WINT_TYPE);
				} else {
					value->uint = va_arg(ap, unsigned long);
				}
				break;
			case LENGTH_LL:
				value->uint =
					(uint_value_type)va_arg(ap,
						unsigned long long);
				break;
			case LENGTH_J:
				value->uint =
					(uint_value_type)va_arg(ap,
								uintmax_t);
				break;
			case LENGTH_Z:		/* size_t */
			case LENGTH_T:		/* ptrdiff_t */
				value->uint =
					(uint_value_type)va_arg(ap, size_t);
				break;
			}
			if (length_mod == LENGTH_HH) {
				value->uint = (unsigned char)value->uint;
			} else if (length_mod == LENGTH_H) {
				value->uint = (unsigned short)value->uint;
			}
		} else if (specifier_cat == SPECIFIER_FP) {
			if (length_mod == LENGTH_UPPER_L) {
				value->ldbl = va_arg(ap, long double);
			} else {
				value->dbl = va_arg(ap, double);
			}
		} else if (specifier_cat == SPECIFIER_PTR) {
			value->ptr = va_arg(ap, void *);
		}

		/* We've now consumed all arguments related to this
		 * specification.  If the conversion is invalid, or is
		 * something we don't support, then output the original
		 * specification and move on.
		 */
		if (conv->invalid || conv->unsupported) {
			OUTS(sp, fp);
			continue;
		}

		/* Do formatting, either into the buffer or
		 * referencing external data.
		 */
		switch (conv->specifier) {
		case '%':
			OUTC('%');
			break;
		case 's': {
			bps = (const char *)value->ptr;

			size_t len;

			if (precision >= 0) {
				len = strnlen(bps, precision);
			} else {
				len = strlen(bps);
			}

			bpe = bps + len;
			precision = -1;

			break;
		}
		case 'c':
			bps = buf;
			buf[0] = CHAR_IS_SIGNED ? value->sint : value->uint;
			bpe = buf + 1;
			break;
		case 'd':
		case 'i':
			if (conv->flag_plus) {
				sign = '+';
			} else if (conv->flag_space) {
				sign = ' ';
			}

			/* sint/uint overlay in the union, and so
			 * can't appear in read and write operations
			 * in the same statement.
			 */
			sint = value->sint;
			if (sint < 0) {
				sign = '-';
				value->uint = (uint_value_type)-sint;
			} else {
				value->uint = (uint_value_type)sint;
			}

			__fallthrough;
		case 'o':
		case 'u':
		case 'x':
		case 'X':
			bps = encode_uint(value->uint, conv, buf, bpe);

		prec_int_pad0:
			/* Update pad0 values based on precision and converted
			 * length.  Note that a non-empty sign is not in the
			 * converted sequence, but it does not affect the
			 * padding size.
			 */
			if (precision >= 0) {
				size_t len = bpe - bps;

				/* Zero-padding flag is ignored for integer
				 * conversions with precision.
				 */
				conv->flag_zero = false;

				/* Set pad0_value to satisfy precision */
				if (len < (size_t)precision) {
					conv->pad0_value = precision - (int)len;
				}
			}

			break;
		case 'p':
			/* Implementation-defined: null is "(nil)", non-null
			 * has 0x prefix followed by significant address hex
			 * digits, no leading zeros.
			 */
			if (value->ptr != NULL) {
				bps = encode_uint((uintptr_t)value->ptr, conv,
						  buf, bpe);

				/* Use 0x prefix */
				conv->altform_0c = true;
				conv->specifier = 'x';

				goto prec_int_pad0;
			}

			bps = "(nil)";
			bpe = bps + 5;

			break;
		case 'n':
			if (IS_ENABLED(CONFIG_CBPRINTF_N_SPECIFIER)) {
				store_count(conv, value->ptr, count);
			}

			break;

		case FP_CONV_CASES:
			if (IS_ENABLED(CONFIG_CBPRINTF_FP_SUPPORT)) {
				bps = encode_float(value->dbl, conv, precision,
						   &sign, buf, &bpe);
			}
			break;
		default:
			/* Add an empty default with break, this is a defensive
			 * programming. Static analysis tool won't raise a violation
			 * if default is empty, but has that comment.
			 */
			break;
		}

		/* If we don't have a converted value to emit, move
		 * on.
		 */
		if (bps == NULL) {
			continue;
		}

		/* The converted value is now stored in [bps, bpe), excluding
		 * any required zero padding.
		 *
		 * The unjustified output will be:
		 *
		 * * any sign character (sint-only)
		 * * any altform prefix
		 * * for FP:
		 *   * any pre-decimal content from the converted value
		 *   * any pad0_value padding (!postdp)
		 *   * any decimal point in the converted value
		 *   * any pad0_value padding (postdp)
		 *   * any pre-exponent content from the converted value
		 *   * any pad0_pre_exp padding
		 *   * any exponent content from the converted value
		 * * for non-FP:
		 *   * any pad0_prefix
		 *   * the converted value
		 */
		size_t nj_len = (bpe - bps);
		int pad_len = 0;

		if (sign != 0) {
			nj_len += 1U;
		}

		if (conv->altform_0c) {
			nj_len += 2U;
		} else if (conv->altform_0) {
			nj_len += 1U;
		}

		nj_len += conv->pad0_value;
		if (conv->pad_fp) {
			nj_len += conv->pad0_pre_exp;
		}

		/* If we have a width update width to hold the padding we need
		 * for justification.  The result may be negative, which will
		 * result in no padding.
		 *
		 * If a non-negative padding width is present and we're doing
		 * right-justification, emit the padding now.
		 */
		if (width > 0) {
			width -= (int)nj_len;

			if (!conv->flag_dash) {
				char pad = ' ';

				/* If we're zero-padding we have to emit the
				 * sign first.
				 */
				if (conv->flag_zero) {
					if (sign != 0) {
						OUTC(sign);
						sign = 0;
					}
					pad = '0';
				}

				while (width-- > 0) {
					OUTC(pad);
				}
			}
		}

		/* If we have a sign that hasn't been emitted, now's the
		 * time....
		 */
		if (sign != 0) {
			OUTC(sign);
		}

		if (IS_ENABLED(CONFIG_CBPRINTF_FP_SUPPORT) && conv->pad_fp) {
			const char *cp = bps;

			if (conv->specifier_a) {
				/* Only padding is pre_exp */
				while (*cp != 'p') {
					OUTC(*cp++);
				}
			} else {
				while (isdigit((unsigned char)*cp) != 0) {
					OUTC(*cp++);
				}

				pad_len = conv->pad0_value;
				if (!conv->pad_postdp) {
					while (pad_len-- > 0) {
						OUTC('0');
					}
				}

				if (*cp == '.') {
					OUTC(*cp++);
					/* Remaining padding is
					 * post-dp.
					 */
					while (pad_len-- > 0) {
						OUTC('0');
					}
				}
				while (isdigit((unsigned char)*cp) != 0) {
					OUTC(*cp++);
				}
			}

			pad_len = conv->pad0_pre_exp;
			while (pad_len-- > 0) {
				OUTC('0');
			}

			OUTS(cp, bpe);
		} else {
			if (conv->altform_0c | conv->altform_0) {
				OUTC('0');
			}

			if (conv->altform_0c) {
				OUTC(conv->specifier);
			}

			pad_len = conv->pad0_value;
			while (pad_len-- > 0) {
				OUTC('0');
			}

			OUTS(bps, bpe);
		}

		/* Finish left justification */
		while (width > 0) {
			OUTC(' ');
			--width;
		}
	}

	return count;
#undef OUTS
#undef OUTC
}
