1845 lines
41 KiB
C
1845 lines
41 KiB
C
/*
|
|
* 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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/* 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 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
|
|
}
|