diff options
Diffstat (limited to 'src/std_strlprintf.c')
-rw-r--r-- | src/std_strlprintf.c | 759 |
1 files changed, 759 insertions, 0 deletions
diff --git a/src/std_strlprintf.c b/src/std_strlprintf.c new file mode 100644 index 0000000..c927dc8 --- /dev/null +++ b/src/std_strlprintf.c @@ -0,0 +1,759 @@ +/* + * Copyright (c) 2019, The Linux Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of The Linux Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "AEEstd.h" +#include "AEEBufBound.h" +#include "AEEsmath.h" +#include "AEEStdErr.h" +#include "std_dtoa.h" +//#include "math.h" + +//============================================================================== +// Macro definitions +//============================================================================== + +#define ISDIGIT(c) ( (c) >= '0' && (c) <= '9') +#define TOLOWER(c) ( (c) | 32 ) // works only for letters +#define FAILED(b) ( (b) != AEE_SUCCESS ? TRUE : FALSE ) +#define CLEANUP_ON_ERROR(b,l) if( FAILED( b ) ) { goto l; } +#define ROUND(d, p) fp_round( d, p ) +#define FP_POW_10(n) fp_pow_10(n) + +//============================================================================== +// Type definitions +//============================================================================== + + +// Formatting flags + +#define FF_PLUS 1 // '+' +#define FF_MINUS 2 // '-' +#define FF_POUND 4 // '#' +#define FF_BLANK 8 // ' ' +#define FF_ZERO 16 // '0' + +typedef struct { + + // Parsed values (from "%..." expression) + + int flags; // FF_PLUS, FF_MINUS, etc. + char cType; // d, s, c, x, X, etc. + int32 nWidth; // number preceding '.' : controls padding + int32 nPrecision; // number following '.' (-1 if not given) + + // Computed values + + const char * pszStr; // string holding prefix + value + int nPrefix; // # of numeric prefix bytes in pszStr[] + int nLen; // length of string (after prefix) + int nNumWidth; // minimum numeric value size (pad with '0') + +} FieldFormat; + +typedef int (*pfnFormatFloat)(FieldFormat* me, double dNumber, char* pcBuffer); + +//============================================================================== +// Function definitions +//============================================================================== + +// Read an unsigned decimal integer +// +static int ScanDecimal(const char **ppsz) +{ + int n = 0; + const char *psz; + + for (psz = *ppsz; ISDIGIT(*psz); ++psz) { + n = n*10 + (int) (*psz - '0'); + } + *ppsz = psz; + return n; +} + + +#define FORMATNUMBER_SIZE 24 // octal: 22 + '0' + null ; decimal: 20 + sign + null + + +// Convert number to string, setting computed fields in FieldFormat. +// +// pcBuf[] must have room for at least FORMATNUMBER_SIZE characters +// return value: length of string. +// +static __inline void +FormatNumber(FieldFormat *me, char pcBuf[FORMATNUMBER_SIZE], uint64 uNum64) +{ + char cType = me->cType; + const char *cpszDigits; + char *pc = pcBuf; + int nBase; + char *pcRev; + + if (cType == 'p') { + cType = 'X'; + me->nPrecision = 8; + } + + if (me->nPrecision >= 0) { + me->nNumWidth = me->nPrecision; + // Odd thing: '0' flag is ignored for numbers when precision is + // specified. + me->flags &= ~FF_ZERO; + } else { + me->nNumWidth = 1; + } + + // Output prefix + + if (( 'd' == cType || 'i' == cType)) { + if ((int64)uNum64 < 0) { + *pc++ = '-'; + uNum64 = (uint64)-(int64)uNum64; + } else if (me->flags & FF_PLUS) { + *pc++ = '+'; + } else if (me->flags & FF_BLANK) { + *pc++ = ' '; + } + } + + if ((me->flags & FF_POUND) && 0 != uNum64) { + if ('x' == TOLOWER(cType)) { + *pc++ = '0'; + *pc++ = cType; + } else if ('o' == cType) { + *pc++ = '0'; + // Odd thing about libc printf: "0" prefix counts as part of minimum + // width, but "0x" prefix does not. + --me->nNumWidth; + } + } + me->nPrefix = pc - pcBuf; + + // Output unsigned numeric value + + nBase = ('o' == cType ? 8 : + 'x' == TOLOWER(cType) ? 16 : + 10); + cpszDigits = ((cType == 'X') ? "0123456789ABCDEF" + : "0123456789abcdef"); + + pcRev = pc; + + while (uNum64) { + *pc++ = cpszDigits[uNum64 % (unsigned)nBase]; + uNum64 /= (unsigned)nBase; + } + + *pc = '\0'; + + me->pszStr = pcBuf; + me->nLen = pc - pcRev; + + // Reverse string + + --pc; + for (; pcRev < pc; ++pcRev, --pc) { + char c = *pc; + *pc = *pcRev; + *pcRev = c; + } +} + +// +// This function converts the input floating point number dNumber to an +// ASCII string using either %f or %F formatting. This functions assumes +// that dNumer is a valid floating point number (i.e., dNumber is NOT +// +/-INF or NaN). The size of the output buffer pcBuffer should be at +// least STD_DTOA_FORMAT_FLOAT_SIZE. +// +static int ConvertFloat(FieldFormat* me, double dNumber, char* pcBuffer, + int nBufSize) +{ + int nError = AEE_SUCCESS; + int32 nPrecision = 0; + int nIndex = 0; + BufBound OutBuf; + char szIntegerPart[STD_DTOA_FORMAT_INTEGER_SIZE] = {0}; + char szFractionPart[STD_DTOA_FORMAT_FRACTION_SIZE] = {0}; + int nExponent = 0; + char cType = TOLOWER(me->cType); + + // Set the precision for conversion + nPrecision = me->nPrecision; + if (nPrecision < 0) { + // No precision was specified, set it to the default value if the + // format specifier is not %a + if (cType != 'a') { + nPrecision = STD_DTOA_DEFAULT_FLOAT_PRECISION; + } + } + else if ((0 == nPrecision) && ('g' == cType)) { + nPrecision = 1; + } + + if (cType != 'a') { + // For %g, check whether to use %e of %f formatting style. + // Also, set the precision value accordingly since in this case the user + // specified value is really the number of significant digits. + // These next few steps should be skipped if the input number is 0. + if (dNumber != 0.0) { + nExponent = fp_log_10(dNumber); + if ('g' == cType) { + if ((nExponent < -4) || (nExponent >= nPrecision)) { + cType = 'e'; + nPrecision = nPrecision - 1; + } + else { + cType = 'f'; + nPrecision = nPrecision - nExponent - 1; + } + } + + // For %e, convert the number to the form d.ddd + if ('e' == cType) { + dNumber = dNumber / FP_POW_10(nExponent); + } + + // Now, round the number to the specified precision + dNumber = ROUND(dNumber, nPrecision); + + // For %e, the rounding operation may have resulted in a number dd.ddd + // Reconvert it to the form d.ddd + if (('e' == cType) && ((dNumber >= 10.0) || (dNumber <= -10.0))) { + dNumber = dNumber / 10.0; + nExponent++; + } + } + + // Convert the decmial number to string + nError = std_dtoa_decimal(dNumber, nPrecision, szIntegerPart, szFractionPart); + CLEANUP_ON_ERROR(nError, bail); + } + else + { + // Conver the hex floating point number to string + nError = std_dtoa_hex(dNumber, nPrecision, me->cType, szIntegerPart, + szFractionPart, &nExponent); + CLEANUP_ON_ERROR(nError, bail); + } + + + // + // Write the output as per the specified format. + // First: Check for any prefixes that need to be added to the output. + // The only possible prefixes are '-', '+' or ' '. The following rules + // are applicable: + // 1. One and only one prefix will be applicable at any time. + // 2. If the number is negative, then '+' and ' ' are not applicable. + // 3. For positive numbers, the prefix '+' takes precedence over ' '. + // + // In addition, we were dealing with a hex floating point number (%a), + // then we need to write of the 0x prefix. + // + BufBound_Init(&OutBuf, pcBuffer, nBufSize); + if (dNumber < 0.0) { + // The '-' sign would have already been added to the szIntegerPart by + // the conversion function. + me->nPrefix = 1; + } + if (dNumber >= 0.0){ + if (me->flags & FF_PLUS) { + BufBound_Putc(&OutBuf, '+'); + me->nPrefix = 1; + } + else if(me->flags & FF_BLANK) { + BufBound_Putc(&OutBuf, ' '); + me->nPrefix = 1; + } + } + + // For %a, write out the 0x prefix + if ('a' == cType) { + BufBound_Putc(&OutBuf, '0'); + BufBound_Putc(&OutBuf, ('a' == me->cType) ? 'x' : 'X'); + me->nPrefix += 2; + } + + // Second: Write the integer part + BufBound_Puts(&OutBuf, szIntegerPart); + + // Third: Write the decimal point followed by the fraction part. + // For %g, we need to truncate the trailing zeros in the fraction. + // Skip this if the '#' flag is specified + if (!(me->flags & FF_POUND) && ('g' == TOLOWER(me->cType))) { + for (nIndex = std_strlen(szFractionPart) - 1; + (nIndex >= 0) && (szFractionPart[nIndex] == '0'); nIndex--) { + szFractionPart[nIndex] = '\0'; + } + } + + // The decimal point is specified only if there are some decimal digits. + // However, if the '#' format specifier is present then the decimal point + // will be present. + if ((me->flags & FF_POUND) || (*szFractionPart != 0)) { + BufBound_Putc(&OutBuf, '.'); + + // Write the fraction part + BufBound_Puts(&OutBuf, szFractionPart); + } + + // For %e and %a, write out the exponent + if (('e' == cType) || ('a' == cType)) { + char* pcExpStart = NULL; + char* pcExpEnd = NULL; + char cTemp = 0; + + if ('a' == me->cType) { + BufBound_Putc(&OutBuf, 'p'); + } + else if ('A' == me->cType) { + BufBound_Putc(&OutBuf, 'P'); + } + else if (('e' == me->cType) || ('g' == me->cType)) { + BufBound_Putc(&OutBuf, 'e'); + } + else { + BufBound_Putc(&OutBuf, 'E'); + } + + // Write the exponent sign + if (nExponent < 0) { + BufBound_Putc(&OutBuf, '-'); + nExponent = -nExponent; + } + else { + BufBound_Putc(&OutBuf, '+'); + } + + // Write out the exponent. + // For %e, the exponent should at least be two digits. + // The exponent to be written will be at most 4 digits as any + // overflow would have been take care of by now. + if (BufBound_Left(&OutBuf) >= 4) { + if ('e' == cType) { + if (nExponent < 10) { + BufBound_Putc(&OutBuf, '0'); + } + } + + pcExpStart = OutBuf.pcWrite; + do { + BufBound_Putc(&OutBuf, '0' + (nExponent % 10)); + nExponent /= 10; + } while (nExponent); + pcExpEnd = OutBuf.pcWrite - 1; + + // Reverse the exponent + for (; pcExpStart < pcExpEnd; pcExpStart++, pcExpEnd--) { + cTemp = *pcExpStart; + *pcExpStart = *pcExpEnd; + *pcExpEnd = cTemp; + } + } + } + + // Null-terminate the string + BufBound_ForceNullTerm(&OutBuf); + + // Set the output parameters + // We do not care if there was enough space in the output buffer or not. + // The output would be truncated to a maximum length of + // STD_DTOA_FORMAT_FLOAT_SIZE. + me->pszStr = OutBuf.pcBuf; + me->nLen = BufBound_ReallyWrote(&OutBuf) - me->nPrefix - 1; + +bail: + + return nError; +} + +// +// This is a wrapper function that converts an input floating point number +// to a string based on a given format specifier %e, %f or %g. It first checks +// if the specified number is a valid floating point number before calling +// the function that does the conversion. +// +// The size of the output buffer pcBuffer should be at least STD_DTOA_FORMAT_FLOAT_SIZE. +// +static int FormatFloat(FieldFormat* me, double dNumber, + char pcBuffer[STD_DTOA_FORMAT_FLOAT_SIZE]) +{ + int nError = AEE_SUCCESS; + FloatingPointType NumberType = FP_TYPE_UNKOWN; + + // Check for error conditions + if (NULL == pcBuffer) { + nError = AEE_EBADPARM; + goto bail; + } + + // Initialize the output params first + me->nLen = 0; + me->nPrefix = 0; + + // Check for special cases such as NaN and Infinity + nError = fp_check_special_cases(dNumber, &NumberType); + CLEANUP_ON_ERROR(nError, bail); + + switch(NumberType) { + case FP_TYPE_NEGATIVE_INF: + + if (('E' == me->cType) || ('F' == me->cType) || ('G' == me->cType)) { + me->nLen = std_strlcpy(pcBuffer, STD_DTOA_NEGATIVE_INF_UPPER_CASE, + STD_DTOA_FORMAT_FLOAT_SIZE); + } + else { + me->nLen = std_strlcpy(pcBuffer, STD_DTOA_NEGATIVE_INF_LOWER_CASE, + STD_DTOA_FORMAT_FLOAT_SIZE); + } + + // Don't pad with 0's + me->flags &= ~FF_ZERO; + + break; + + case FP_TYPE_POSITIVE_INF: + + if (('E' == me->cType) || ('F' == me->cType) || ('G' == me->cType)) { + me->nLen = std_strlcpy(pcBuffer, STD_DTOA_POSITIVE_INF_UPPER_CASE, + STD_DTOA_FORMAT_FLOAT_SIZE); + } + else { + me->nLen = std_strlcpy(pcBuffer, STD_DTOA_POSITIVE_INF_LOWER_CASE, + STD_DTOA_FORMAT_FLOAT_SIZE); + } + + // Don't pad with 0's + me->flags &= ~FF_ZERO; + + break; + + case FP_TYPE_NAN: + + if (('E' == me->cType) || ('F' == me->cType) || ('G' == me->cType)) { + me->nLen = std_strlcpy(pcBuffer, STD_DTOA_NAN_UPPER_CASE, + STD_DTOA_FORMAT_FLOAT_SIZE); + } + else + { + me->nLen = std_strlcpy(pcBuffer, STD_DTOA_NAN_LOWER_CASE, + STD_DTOA_FORMAT_FLOAT_SIZE); + } + + // Don't pad with 0's + me->flags &= ~FF_ZERO; + + break; + + case FP_TYPE_GENERAL: + + nError = ConvertFloat(me, dNumber, pcBuffer, + STD_DTOA_FORMAT_FLOAT_SIZE); + CLEANUP_ON_ERROR(nError, bail); + + break; + + default: + + // This should only happen if this function has been modified + // to support other special cases and this block has not been + // updated. + nError = AEE_EFAILED; + goto bail; + } + + // Set the output parameters + me->pszStr = pcBuffer; + + +bail: + + return nError; +} + +static int std_strlprintf_inner(char *pszDest, int nDestSize, + const char *cpszFmt, AEEVaList args, + pfnFormatFloat pfnFormatFloatFunc) +{ + BufBound bb; + const char *pcIn = cpszFmt; + + BufBound_Init(&bb, pszDest, nDestSize); + + for (;;) { + FieldFormat ff; + const char *pcEsc; + char achBuf[FORMATNUMBER_SIZE]; + char achBuf2[STD_DTOA_FORMAT_FLOAT_SIZE]; + char cType; + boolean bLong = 0; + + pcEsc = std_strchrend(pcIn, '%'); + BufBound_Write(&bb, pcIn, pcEsc-pcIn); + + if (0 == *pcEsc) { + break; + } + pcIn = pcEsc+1; + + //---------------------------------------------------- + // Consume "%..." specifiers: + // + // %[FLAGS] [WIDTH] [.PRECISION] [{h | l | I64 | L}] + //---------------------------------------------------- + + std_memset(&ff, 0, sizeof(FieldFormat)); + ff.nPrecision = -1; + + // Consume all flags + for (;;) { + int f; + + f = (('+' == *pcIn) ? FF_PLUS : + ('-' == *pcIn) ? FF_MINUS : + ('#' == *pcIn) ? FF_POUND : + (' ' == *pcIn) ? FF_BLANK : + ('0' == *pcIn) ? FF_ZERO : 0); + + if (0 == f) { + break; + } + + ff.flags |= f; + ++pcIn; + } + + // Consume width + if ('*' == *pcIn) { + AEEVA_ARG(args, ff.nWidth, int32); + pcIn++; + } else { + ff.nWidth = ScanDecimal(&pcIn); + } + if ((ff.flags & FF_MINUS) && ff.nWidth > 0) { + ff.nWidth = -ff.nWidth; + } + + // Consume precision + if ('.' == *pcIn) { + pcIn++; + if ('*' == *pcIn) { // Can be *... (given in int * param) + AEEVA_ARG(args, ff.nPrecision, int32); + pcIn++; + } else { + ff.nPrecision = ScanDecimal(&pcIn); + } + } + + // Consume size designator + { + static const struct { + char szPre[3]; + boolean b64; + } a[] = { + { "l", 0, }, + { "ll", 1, }, + { "L", 1, }, + { "j", 1, }, + { "h", 0, }, + { "hh", 0, }, + { "z", 0 } + }; + + int n = STD_ARRAY_SIZE(a); + + while (--n >= 0) { + const char *psz = std_strbegins(pcIn, a[n].szPre); + if ((const char*)0 != psz) { + pcIn = psz; + bLong = a[n].b64; + break; + } + } + } + + //---------------------------------------------------- + // + // Format output values + // + //---------------------------------------------------- + + ff.cType = cType = *pcIn++; + + if ('s' == cType) { + + // String + char *psz; + + AEEVA_ARG(args, psz, char*); + ff.pszStr = psz; + ff.nLen = std_strlen(psz); + if (ff.nPrecision >= 0 && ff.nPrecision < ff.nLen) { + ff.nLen = ff.nPrecision; + } + + } else if ('c' == cType) { + + // char + AEEVA_ARG(args, achBuf[0], int); + achBuf[1] = '\0'; + ff.pszStr = achBuf; + ff.nLen = 1; + + } else if ('u' == cType || + 'o' == cType || + 'd' == cType || + 'i' == cType || + 'p' == cType || + 'x' == TOLOWER(cType) ) { + + // int + uint64 uArg64; + + if (bLong) { + AEEVA_ARG(args, uArg64, int64); // See how much room needed + } else { + uint32 uArg32; + AEEVA_ARG(args, uArg32, int32); // See how much room needed + uArg64 = uArg32; + if ('d' == cType || 'i' == cType) { + uArg64 = (uint64)(int64)(int32)uArg32; + } + } + + FormatNumber(&ff, achBuf, uArg64); + + } else if (pfnFormatFloatFunc && + ('e' == TOLOWER(cType) || + 'f' == TOLOWER(cType) || + 'g' == TOLOWER(cType) || + 'a' == TOLOWER(cType))) { + + // float + int nError = AEE_SUCCESS; + double dNumber; + + AEEVA_ARG(args, dNumber, double); + nError = pfnFormatFloatFunc(&ff, dNumber, achBuf2); + if (FAILED(nError)) { + continue; + } + + } else if ('\0' == cType) { + + // premature end + break; + + } else { + // Unknown type + BufBound_Putc(&bb, cType); + continue; + } + + // FieldFormat computed variables + nWidth controls output + + if (ff.flags & FF_ZERO) { + ff.nNumWidth = ff.nWidth - ff.nPrefix; + } + + { + int nLen1 = ff.nLen; + int nLen2 = STD_MAX(ff.nNumWidth, nLen1) + ff.nPrefix; + + // Putnc() safely ignores negative sizes + BufBound_Putnc(&bb, ' ', smath_Sub(ff.nWidth,nLen2)); + BufBound_Write(&bb, ff.pszStr, ff.nPrefix); + BufBound_Putnc(&bb, '0', smath_Sub(ff.nNumWidth, nLen1)); + BufBound_Write(&bb, ff.pszStr+ff.nPrefix, nLen1); + BufBound_Putnc(&bb, ' ', smath_Sub(-nLen2, ff.nWidth)); + } + } + + AEEVA_END(args); + + BufBound_ForceNullTerm(&bb); + + /* Return number of bytes required regardless if buffer bound was reached */ + + /* Note that we subtract 1 because the NUL byte which was added in + BufBound_ForceNullTerm() is counted as a written byte; the semantics + of both the ...printf() functions and the strl...() functions call for + the NUL byte to be excluded from the count. */ + + return BufBound_Wrote(&bb)-1; +} + +int std_vstrlprintf(char *pszDest, int nDestSize, + const char *cpszFmt, + AEEVaList args) +{ + return std_strlprintf_inner(pszDest, nDestSize, cpszFmt, args, NULL); +} + +int std_vsnprintf(char *pszDest, int nDestSize, + const char *cpszFmt, + AEEVaList args) +/* + Same as std_vstrlprintf with the additional support of floating point + conversion specifiers - %e, %f, %g and %a +*/ +{ + return std_strlprintf_inner(pszDest, nDestSize, cpszFmt, args, FormatFloat); +} + +int std_strlprintf(char *pszDest, int nDestSize, const char *pszFmt, ...) +{ + int nRet; + AEEVaList args; + + AEEVA_START(args, pszFmt); + + nRet = std_vstrlprintf(pszDest, nDestSize, pszFmt, args); + + AEEVA_END(args); + + return nRet; +} + +int std_snprintf(char *pszDest, int nDestSize, const char *pszFmt, ...) +/* + Same as std_strlprintf with the additional support of floating point + conversion specifiers - %e, %f, %g and %a +*/ +{ + int nRet; + AEEVaList args; + + AEEVA_START(args, pszFmt); + + nRet = std_vsnprintf(pszDest, nDestSize, pszFmt, args); + + AEEVA_END(args); + + return nRet; +} |