| /* ----> DO NOT REMOVE THE FOLLOWING NOTICE <---- | |
| Copyright (c) 2014-2015 Datalight, Inc. | |
| All Rights Reserved Worldwide. | |
| This program is free software; you can redistribute it and/or modify | |
| it under the terms of the GNU General Public License as published by | |
| the Free Software Foundation; use version 2 of the License. | |
| This program is distributed in the hope that it will be useful, | |
| but "AS-IS," WITHOUT ANY WARRANTY; without even the implied warranty | |
| of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| GNU General Public License for more details. | |
| You should have received a copy of the GNU General Public License along | |
| with this program; if not, write to the Free Software Foundation, Inc., | |
| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
| */ | |
| /* Businesses and individuals that for commercial or other reasons cannot | |
| comply with the terms of the GPLv2 license may obtain a commercial license | |
| before incorporating Reliance Edge into proprietary software for | |
| distribution in any form. Visit http://www.datalight.com/reliance-edge for | |
| more information. | |
| */ | |
| /** @file | |
| @brief Implements functions for printing. | |
| These functions are intended to be used in portable test code, which cannot | |
| assume the standard I/O functions will be available. Similar to their ANSI | |
| C counterparts, these functions allow formatting text strings and (if the | |
| configuration allows it) outputing formatted text. The latter ability | |
| relies on the RedOsOutputString() OS service function. | |
| Do *not* use these functions in code which can safely assume the standard | |
| I/O functions are available (e.g., in host tools code). | |
| Do *not* use these functions from within the file system driver. These | |
| functions use variable arguments and thus are not MISRA-C:2012 compliant. | |
| */ | |
| #include <redfs.h> | |
| #include <redtestutils.h> | |
| #include <limits.h> | |
| #include <stdarg.h> | |
| /** @brief Maximum number of bytes of output supported by RedPrintf(). | |
| Typically only Datalight code uses these functions, and that could should be | |
| written to respect this limit, so it should not normally be necessary to | |
| adjust this value. | |
| */ | |
| #define OUTPUT_BUFFER_SIZE 256U | |
| typedef enum | |
| { | |
| PRFMT_UNKNOWN = 0, | |
| PRFMT_CHAR, | |
| PRFMT_ANSISTRING, | |
| PRFMT_SIGNED8BIT, | |
| PRFMT_UNSIGNED8BIT, | |
| PRFMT_SIGNED16BIT, | |
| PRFMT_UNSIGNED16BIT, | |
| PRFMT_SIGNED32BIT, | |
| PRFMT_UNSIGNED32BIT, | |
| PRFMT_SIGNED64BIT, | |
| PRFMT_UNSIGNED64BIT, | |
| PRFMT_HEX8BIT, | |
| PRFMT_HEX16BIT, | |
| PRFMT_HEX32BIT, | |
| PRFMT_HEX64BIT, | |
| PRFMT_POINTER, | |
| PRFMT_DOUBLEPERCENT | |
| } PRINTTYPE; | |
| typedef struct | |
| { | |
| PRINTTYPE type; /* The PRFMT_* type found */ | |
| uint32_t ulSpecifierIdx; /* Returns a pointer to the % sign */ | |
| uint32_t ulFillLen; | |
| char cFillChar; | |
| bool fLeftJustified; | |
| bool fHasIllegalType; /* TRUE if an illegal sequence was skipped over */ | |
| bool fHasVarWidth; | |
| } PRINTFORMAT; | |
| /* Our output handlers are written for standard fixed width data types. Map | |
| the standard ANSI C data types onto our handlers. Currently this code has | |
| the following requirements: | |
| 1) shorts must be either 16 or 32 bits | |
| 2) ints must be either 16 or 32 bits | |
| 3) longs must be between 32 or 64 bits | |
| 4) long longs must be 64 bits | |
| */ | |
| #if (USHRT_MAX == 0xFFFFU) | |
| #define MAPSHORT PRFMT_SIGNED16BIT | |
| #define MAPUSHORT PRFMT_UNSIGNED16BIT | |
| #define MAPHEXUSHORT PRFMT_HEX16BIT | |
| #elif (USHRT_MAX == 0xFFFFFFFFU) | |
| #define MAPSHORT PRFMT_SIGNED32BIT | |
| #define MAPUSHORT PRFMT_UNSIGNED32BIT | |
| #define MAPHEXUSHORT PRFMT_HEX32BIT | |
| #else | |
| #error "The 'short' data type does not have a 16 or 32-bit width" | |
| #endif | |
| #if (UINT_MAX == 0xFFFFU) | |
| #define MAPINT PRFMT_SIGNED16BIT | |
| #define MAPUINT PRFMT_UNSIGNED16BIT | |
| #define MAPHEXUINT PRFMT_HEX16BIT | |
| #elif (UINT_MAX == 0xFFFFFFFFU) | |
| #define MAPINT PRFMT_SIGNED32BIT | |
| #define MAPUINT PRFMT_UNSIGNED32BIT | |
| #define MAPHEXUINT PRFMT_HEX32BIT | |
| #else | |
| #error "The 'int' data type does not have a 16 or 32-bit width" | |
| #endif | |
| #if (ULONG_MAX == 0xFFFFFFFFU) | |
| #define MAPLONG PRFMT_SIGNED32BIT | |
| #define MAPULONG PRFMT_UNSIGNED32BIT | |
| #define MAPHEXULONG PRFMT_HEX32BIT | |
| #elif (ULONG_MAX <= UINT64_SUFFIX(0xFFFFFFFFFFFFFFFF)) | |
| /* We've run into unusual environments where "longs" are 40-bits wide. | |
| In this event, map them to 64-bit types so no data is lost. | |
| */ | |
| #define MAPLONG PRFMT_SIGNED64BIT | |
| #define MAPULONG PRFMT_UNSIGNED64BIT | |
| #define MAPHEXULONG PRFMT_HEX64BIT | |
| #else | |
| #error "The 'long' data type is not between 32 and 64 bits wide" | |
| #endif | |
| #if defined(ULLONG_MAX) && (ULLONG_MAX != UINT64_SUFFIX(0xFFFFFFFFFFFFFFFF)) | |
| #error "The 'long long' data type is not 64 bits wide" | |
| #else | |
| #define MAPLONGLONG PRFMT_SIGNED64BIT | |
| #define MAPULONGLONG PRFMT_UNSIGNED64BIT | |
| #define MAPHEXULONGLONG PRFMT_HEX64BIT | |
| #endif | |
| static uint32_t ProcessFormatSegment(char *pcBuffer, uint32_t ulBufferLen, const char *pszFormat, PRINTFORMAT *pFormat, uint32_t *pulSpecifierLen); | |
| static uint32_t ParseFormatSpecifier(char const *pszFomat, PRINTFORMAT *pFormatType); | |
| static PRINTTYPE ParseFormatType(const char *pszFormat, uint32_t *pulTypeLen); | |
| static uint32_t LtoA(char *pcBuffer, uint32_t ulBufferLen, int32_t lNum, uint32_t ulFillLen, char cFill); | |
| static uint32_t LLtoA(char *pcBuffer, uint32_t ulBufferLen, int64_t llNum, uint32_t ulFillLen, char cFill); | |
| static uint32_t ULtoA(char *pcBuffer, uint32_t ulBufferLen, uint32_t ulNum, bool fHex, uint32_t ulFillLen, char cFill); | |
| static uint32_t ULLtoA(char *pcBuffer, uint32_t ulBufferLen, uint64_t ullNum, bool fHex, uint32_t ulFillLen, char cFill); | |
| static uint32_t FinishToA(const char *pcDigits, uint32_t ulDigits, char *pcOutBuffer, uint32_t ulBufferLen, uint32_t ulFillLen, char cFill); | |
| /* Digits for the *LtoA() routines. | |
| */ | |
| static const char gacDigits[] = "0123456789ABCDEF"; | |
| #if REDCONF_OUTPUT == 1 | |
| /** @brief Print formatted data with a variable length argument list. | |
| This function provides a subset of the ANSI C printf() functionality with | |
| several extensions to support fixed size data types. | |
| See RedVSNPrintf() for the list of supported types. | |
| @param pszFormat A pointer to the null-terminated format string. | |
| @param ... The variable length argument list. | |
| */ | |
| void RedPrintf( | |
| const char *pszFormat, | |
| ...) | |
| { | |
| va_list arglist; | |
| va_start(arglist, pszFormat); | |
| RedVPrintf(pszFormat, arglist); | |
| va_end(arglist); | |
| } | |
| /** @brief Print formatted data using a pointer to a variable length argument | |
| list. | |
| This function provides a subset of the ANSI C vprintf() functionality. | |
| See RedVSNPrintf() for the list of supported types. | |
| This function accommodates a maximum output length of #OUTPUT_BUFFER_SIZE. | |
| If this function must truncate the output, and the original string was | |
| \n terminated, the truncated output will be \n terminated as well. | |
| @param pszFormat A pointer to the null-terminated format string. | |
| @param arglist The variable length argument list. | |
| */ | |
| void RedVPrintf( | |
| const char *pszFormat, | |
| va_list arglist) | |
| { | |
| char achBuffer[OUTPUT_BUFFER_SIZE]; | |
| if(RedVSNPrintf(achBuffer, sizeof(achBuffer), pszFormat, arglist) == -1) | |
| { | |
| /* Ensture the buffer is null terminated. | |
| */ | |
| achBuffer[sizeof(achBuffer) - 1U] = '\0'; | |
| /* If the original string was \n terminated and the new one is not, due to | |
| truncation, stuff a \n into the new one. | |
| */ | |
| if(pszFormat[RedStrLen(pszFormat) - 1U] == '\n') | |
| { | |
| achBuffer[sizeof(achBuffer) - 2U] = '\n'; | |
| } | |
| } | |
| RedOsOutputString(achBuffer); | |
| } | |
| #endif /* #if REDCONF_OUTPUT == 1 */ | |
| /** @brief Format arguments into a string using a subset of the ANSI C | |
| vsprintf() functionality. | |
| This function is modeled after the Microsoft _snprint() extension to the | |
| ANSI C sprintf() function, and allows a buffer length to be specified so | |
| that overflow is avoided. | |
| See RedVSNPrintf() for the list of supported types. | |
| @param pcBuffer A pointer to the output buffer | |
| @param ulBufferLen The output buffer length | |
| @param pszFormat A pointer to the null terminated format string | |
| @param ... Variable argument list | |
| @return The length output, or -1 if the buffer filled up. If -1 is | |
| returned, the output buffer may not be null-terminated. | |
| */ | |
| int32_t RedSNPrintf( | |
| char *pcBuffer, | |
| uint32_t ulBufferLen, | |
| const char *pszFormat, | |
| ...) | |
| { | |
| int32_t iLen; | |
| va_list arglist; | |
| va_start(arglist, pszFormat); | |
| iLen = RedVSNPrintf(pcBuffer, ulBufferLen, pszFormat, arglist); | |
| va_end(arglist); | |
| return iLen; | |
| } | |
| /** @brief Format arguments into a string using a subset of the ANSI C | |
| vsprintf() functionality. | |
| This function is modeled after the Microsoft _vsnprint() extension to the | |
| ANSI C vsprintf() function, and requires a buffer length to be specified so | |
| that overflow is avoided. | |
| The following ANSI C standard formatting codes are supported: | |
| | Code | Meaning | | |
| | ---- | ---------------------------------- | | |
| | %c | Format a character | | |
| | %s | Format a null-terminated C string | | |
| | %hd | Format a signed short | | |
| | %hu | Format an unsigned short | | |
| | %d | Format a signed integer | | |
| | %u | Format an unsigned integer | | |
| | %ld | Format a signed long | | |
| | %lu | Format an unsigned long | | |
| | %lld | Format a signed long long | | |
| | %llu | Format an unsigned long long | | |
| | %hx | Format a short in hex | | |
| | %x | Format an integer in hex | | |
| | %lx | Format a long in hex | | |
| | %llx | Format a long long in hex | | |
| | %p | Format a pointer (hex value) | | |
| @note All formatting codes are case-sensitive. | |
| Fill characters and field widths are supported per the ANSI standard, as is | |
| left justification with the '-' character. | |
| The only supported fill characters are '0', ' ', and '_'. | |
| '*' is supported to specify variable length field widths. | |
| Hexidecimal numbers are always displayed in upper case. Formatting codes | |
| which specifically request upper case (e.g., "%lX") are not supported. | |
| Unsupported behaviors: | |
| - Precision is not supported. | |
| - Floating point is not supported. | |
| Errata: | |
| - There is a subtle difference in the return value for this function versus | |
| the Microsoft implementation. In the Microsoft version, if the buffer | |
| exactly fills up, but there is no room for a null-terminator, the return | |
| value will be the length of the buffer. In this code, -1 will be returned | |
| when this happens. | |
| - When using left justified strings, the only supported fill character is a | |
| space, regardless of what may be specified. It is not clear if this is | |
| ANSI standard or just the way the Microsoft function works, but we emulate | |
| the Microsoft behavior. | |
| @param pcBuffer A pointer to the output buffer. | |
| @param ulBufferLen The output buffer length. | |
| @param pszFormat A pointer to the null terminated ANSI format string. | |
| @param arglist Variable argument list. | |
| @return The length output, or -1 if the buffer filled up. If -1 is | |
| returned, the output buffer may not be null-terminated. | |
| */ | |
| int32_t RedVSNPrintf( | |
| char *pcBuffer, | |
| uint32_t ulBufferLen, | |
| const char *pszFormat, | |
| va_list arglist) | |
| { | |
| uint32_t ulBufIdx = 0U; | |
| uint32_t ulFmtIdx = 0U; | |
| int32_t iLen; | |
| while((pszFormat[ulFmtIdx] != '\0') && (ulBufIdx < ulBufferLen)) | |
| { | |
| PRINTFORMAT fmt; | |
| uint32_t ulSpecifierLen; | |
| uint32_t ulWidth; | |
| /* Process the next segment of the format string, outputting | |
| any non-format specifiers, as output buffer space allows, | |
| and return information about the next format specifier. | |
| */ | |
| ulWidth = ProcessFormatSegment(&pcBuffer[ulBufIdx], ulBufferLen - ulBufIdx, &pszFormat[ulFmtIdx], &fmt, &ulSpecifierLen); | |
| if(ulWidth) | |
| { | |
| REDASSERT(ulWidth <= (ulBufferLen - ulBufIdx)); | |
| ulBufIdx += ulWidth; | |
| } | |
| /* If no specifier was found, or if the output buffer is | |
| full, we're done -- get out. | |
| */ | |
| if((ulSpecifierLen == 0U) || (ulBufIdx == ulBufferLen)) | |
| { | |
| break; | |
| } | |
| /* Otherwise, the math should add up for these things... | |
| */ | |
| REDASSERT(&pszFormat[fmt.ulSpecifierIdx] == &pszFormat[ulWidth]); | |
| /* Point past the specifier, to the next piece of the format string. | |
| */ | |
| ulFmtIdx = ulFmtIdx + fmt.ulSpecifierIdx + ulSpecifierLen; | |
| if(fmt.fHasVarWidth) | |
| { | |
| int iFillLen = va_arg(arglist, int); | |
| if(iFillLen >= 0) | |
| { | |
| fmt.ulFillLen = (uint32_t)iFillLen; | |
| } | |
| else | |
| { | |
| /* Bogus fill length. Ignore. | |
| */ | |
| fmt.ulFillLen = 0U; | |
| } | |
| } | |
| switch(fmt.type) | |
| { | |
| case PRFMT_DOUBLEPERCENT: | |
| { | |
| /* Nothing to do. A single percent has already been output, | |
| and we just finished skipping past the second percent. | |
| */ | |
| break; | |
| } | |
| /*-----------------> Small int handling <------------------ | |
| * | |
| * Values smaller than "int" will be promoted to "int" by | |
| * the compiler, so we must retrieve them using "int" when | |
| * calling va_arg(). Once we've done that, we immediately | |
| * put the value into the desired data type. | |
| *---------------------------------------------------------*/ | |
| case PRFMT_CHAR: | |
| { | |
| pcBuffer[ulBufIdx] = (char)va_arg(arglist, int); | |
| ulBufIdx++; | |
| break; | |
| } | |
| case PRFMT_SIGNED8BIT: | |
| { | |
| int8_t num = (int8_t)va_arg(arglist, int); | |
| ulBufIdx += LtoA(&pcBuffer[ulBufIdx], ulBufferLen - ulBufIdx, num, fmt.ulFillLen, fmt.cFillChar); | |
| break; | |
| } | |
| case PRFMT_UNSIGNED8BIT: | |
| { | |
| uint8_t bNum = (uint8_t)va_arg(arglist, unsigned); | |
| ulBufIdx += ULtoA(&pcBuffer[ulBufIdx], ulBufferLen - ulBufIdx, bNum, false, fmt.ulFillLen, fmt.cFillChar); | |
| break; | |
| } | |
| case PRFMT_HEX8BIT: | |
| { | |
| uint8_t bNum = (uint8_t)va_arg(arglist, unsigned); | |
| ulBufIdx += ULtoA(&pcBuffer[ulBufIdx], ulBufferLen - ulBufIdx, bNum, true, fmt.ulFillLen, fmt.cFillChar); | |
| break; | |
| } | |
| case PRFMT_SIGNED16BIT: | |
| { | |
| int16_t num = (int16_t)va_arg(arglist, int); | |
| ulBufIdx += LtoA(&pcBuffer[ulBufIdx], ulBufferLen - ulBufIdx, num, fmt.ulFillLen, fmt.cFillChar); | |
| break; | |
| } | |
| case PRFMT_UNSIGNED16BIT: | |
| { | |
| uint16_t uNum = (uint16_t)va_arg(arglist, unsigned); | |
| ulBufIdx += ULtoA(&pcBuffer[ulBufIdx], ulBufferLen - ulBufIdx, uNum, false, fmt.ulFillLen, fmt.cFillChar); | |
| break; | |
| } | |
| case PRFMT_HEX16BIT: | |
| { | |
| uint16_t uNum = (uint16_t)va_arg(arglist, unsigned); | |
| ulBufIdx += ULtoA(&pcBuffer[ulBufIdx], ulBufferLen - ulBufIdx, uNum, true, fmt.ulFillLen, fmt.cFillChar); | |
| break; | |
| } | |
| case PRFMT_SIGNED32BIT: | |
| { | |
| int32_t lNum = va_arg(arglist, int32_t); | |
| ulBufIdx += LtoA(&pcBuffer[ulBufIdx], ulBufferLen - ulBufIdx, lNum, fmt.ulFillLen, fmt.cFillChar); | |
| break; | |
| } | |
| case PRFMT_UNSIGNED32BIT: | |
| { | |
| uint32_t ulNum = va_arg(arglist, uint32_t); | |
| ulBufIdx += ULtoA(&pcBuffer[ulBufIdx], ulBufferLen - ulBufIdx, ulNum, false, fmt.ulFillLen, fmt.cFillChar); | |
| break; | |
| } | |
| case PRFMT_HEX32BIT: | |
| { | |
| uint32_t ulNum = va_arg(arglist, uint32_t); | |
| ulBufIdx += ULtoA(&pcBuffer[ulBufIdx], ulBufferLen - ulBufIdx, ulNum, true, fmt.ulFillLen, fmt.cFillChar); | |
| break; | |
| } | |
| case PRFMT_SIGNED64BIT: | |
| { | |
| int64_t llNum = va_arg(arglist, int64_t); | |
| ulBufIdx += LLtoA(&pcBuffer[ulBufIdx], ulBufferLen - ulBufIdx, llNum, fmt.ulFillLen, fmt.cFillChar); | |
| break; | |
| } | |
| case PRFMT_UNSIGNED64BIT: | |
| { | |
| uint64_t ullNum = va_arg(arglist, uint64_t); | |
| ulBufIdx += ULLtoA(&pcBuffer[ulBufIdx], ulBufferLen - ulBufIdx, ullNum, false, fmt.ulFillLen, fmt.cFillChar); | |
| break; | |
| } | |
| case PRFMT_HEX64BIT: | |
| { | |
| uint64_t ullNum = va_arg(arglist, uint64_t); | |
| ulBufIdx += ULLtoA(&pcBuffer[ulBufIdx], ulBufferLen - ulBufIdx, ullNum, true, fmt.ulFillLen, fmt.cFillChar); | |
| break; | |
| } | |
| case PRFMT_POINTER: | |
| { | |
| const void *ptr = va_arg(arglist, const void *); | |
| /* Assert our assumption. | |
| */ | |
| REDASSERT(sizeof(void *) <= 8U); | |
| /* Format as either a 64-bit or a 32-bit value. | |
| */ | |
| if(sizeof(void *) > 4U) | |
| { | |
| /* Attempt to quiet warnings. | |
| */ | |
| uintptr_t ptrval = (uintptr_t)ptr; | |
| uint64_t ullPtrVal = (uint64_t)ptrval; | |
| ulBufIdx += ULLtoA(&pcBuffer[ulBufIdx], ulBufferLen - ulBufIdx, ullPtrVal, true, fmt.ulFillLen, fmt.cFillChar); | |
| } | |
| else | |
| { | |
| /* Attempt to quiet warnings. | |
| */ | |
| uintptr_t ptrval = (uintptr_t)ptr; | |
| uint32_t ulPtrVal = (uint32_t)ptrval; | |
| ulBufIdx += ULtoA(&pcBuffer[ulBufIdx], ulBufferLen - ulBufIdx, ulPtrVal, true, fmt.ulFillLen, fmt.cFillChar); | |
| } | |
| break; | |
| } | |
| case PRFMT_ANSISTRING: | |
| { | |
| const char *pszArg = va_arg(arglist, const char *); | |
| uint32_t ulArgIdx = 0U; | |
| if(pszArg == NULL) | |
| { | |
| pszArg = "null"; | |
| } | |
| if(fmt.ulFillLen > 0U) | |
| { | |
| if(!fmt.fLeftJustified) | |
| { | |
| uint32_t ulLen = RedStrLen(pszArg); | |
| /* So long as we are not left justifying, fill as many | |
| characters as is necessary to make the string right | |
| justified. | |
| */ | |
| while(((ulBufferLen - ulBufIdx) > 0U) && (fmt.ulFillLen > ulLen)) | |
| { | |
| pcBuffer[ulBufIdx] = fmt.cFillChar; | |
| ulBufIdx++; | |
| fmt.ulFillLen--; | |
| } | |
| } | |
| /* Move as many characters as we have space for into the | |
| output buffer. | |
| */ | |
| while(((ulBufferLen - ulBufIdx) > 0U) && (pszArg[ulArgIdx] != '\0')) | |
| { | |
| pcBuffer[ulBufIdx] = pszArg[ulArgIdx]; | |
| ulBufIdx++; | |
| ulArgIdx++; | |
| if(fmt.ulFillLen > 0U) | |
| { | |
| fmt.ulFillLen--; | |
| } | |
| } | |
| /* If there is any space left to fill, do it (the string | |
| must have been left justified). | |
| */ | |
| while(((ulBufferLen - ulBufIdx) > 0U) && (fmt.ulFillLen > 0U)) | |
| { | |
| /* This is NOT a typo -- when using left justified | |
| strings, spaces are the only allowed fill character. | |
| See the errata. | |
| */ | |
| pcBuffer[ulBufIdx] = ' '; | |
| ulBufIdx++; | |
| fmt.ulFillLen--; | |
| } | |
| } | |
| else | |
| { | |
| /* No fill characters, just move up to as many | |
| characters as we have space for in the output | |
| buffer. | |
| */ | |
| while(((ulBufferLen - ulBufIdx) > 0U) && (pszArg[ulArgIdx] != '\0')) | |
| { | |
| pcBuffer[ulBufIdx] = pszArg[ulArgIdx]; | |
| ulBufIdx++; | |
| ulArgIdx++; | |
| } | |
| } | |
| break; | |
| } | |
| default: | |
| { | |
| REDERROR(); | |
| break; | |
| } | |
| } | |
| } | |
| /* If there is space, tack on a null and return the output length | |
| processed, not including the null. | |
| */ | |
| if(ulBufIdx < ulBufferLen) | |
| { | |
| pcBuffer[ulBufIdx] = '\0'; | |
| iLen = (int32_t)ulBufIdx; | |
| } | |
| else | |
| { | |
| /* Not enough space, just return -1, with no null termination | |
| */ | |
| iLen = -1; | |
| } | |
| return iLen; | |
| } | |
| /** @brief Process the next segment of the format string, outputting any | |
| non-format specifiers, as output buffer space allows, and return | |
| information about the next format specifier. | |
| @note If the returned value is the same as the supplied @p ulBufferLen, | |
| the output buffer will not be null-terminated. In all other cases, | |
| the result will be null-terminated. The returned length will never | |
| include the null in the count. | |
| @param pcBuffer The output buffer. | |
| @param ulBufferLen The output buffer length. | |
| @param pszFormat The format string to process. | |
| @param pFormat The PRINTFORMAT structure to fill. | |
| @param pulSpecifierLen Returns the length of any format specifier string, | |
| or zero if no specifier was found. | |
| @return The count of characters from pszFormatt which were processed and | |
| copied to pcBuffer. | |
| - If zero is returned and *pulSpecifierLen is non-zero, then | |
| a format specifier string was found at the start of pszFmt. | |
| - If non-zero is returned and *pulSpecifierLen is zero, then | |
| no format specifier string was found, and the entire pszFmt | |
| string was copied to pBuffer (or as much as will fit). | |
| */ | |
| static uint32_t ProcessFormatSegment( | |
| char *pcBuffer, | |
| uint32_t ulBufferLen, | |
| const char *pszFormat, | |
| PRINTFORMAT *pFormat, | |
| uint32_t *pulSpecifierLen) | |
| { | |
| uint32_t ulWidth = 0U; | |
| /* Find the next format specifier string, and information about it. | |
| */ | |
| *pulSpecifierLen = ParseFormatSpecifier(pszFormat, pFormat); | |
| if(*pulSpecifierLen == 0U) | |
| { | |
| /* If no specifier was found at all, then simply output the full length | |
| of the string, or as much as will fit. | |
| */ | |
| ulWidth = REDMIN(ulBufferLen, RedStrLen(pszFormat)); | |
| RedMemCpy(pcBuffer, pszFormat, ulWidth); | |
| } | |
| else | |
| { | |
| /* If we encountered a double percent, skip past one of them so it is | |
| copied into the output buffer. | |
| */ | |
| if(pFormat->type == PRFMT_DOUBLEPERCENT) | |
| { | |
| pFormat->ulSpecifierIdx++; | |
| /* A double percent specifier always has a length of two. Since | |
| we're processing one of those percent signs, reduce the length | |
| to one. Assert it so. | |
| */ | |
| REDASSERT(*pulSpecifierLen == 2U); | |
| (*pulSpecifierLen)--; | |
| } | |
| /* So long as the specifier is not the very first thing in the format | |
| string... | |
| */ | |
| if(pFormat->ulSpecifierIdx != 0U) | |
| { | |
| /* A specifier was found, but there is other data preceding it. | |
| Copy as much as allowed to the output buffer. | |
| */ | |
| ulWidth = REDMIN(ulBufferLen, pFormat->ulSpecifierIdx); | |
| RedMemCpy(pcBuffer, pszFormat, ulWidth); | |
| } | |
| } | |
| /* If there is room in the output buffer, null-terminate whatever is there. | |
| But note that the returned length never includes the null. | |
| */ | |
| if(ulWidth < ulBufferLen) | |
| { | |
| pcBuffer[ulWidth] = 0U; | |
| } | |
| return ulWidth; | |
| } | |
| /** @brief Parse the specified format string for a valid RedVSNPrintf() format | |
| sequence, and return information about it. | |
| @param pszFormat The format string to process. | |
| @param pFormatType The PRINTFORMAT structure to fill. The data is only | |
| valid if a non-zero length is returned. | |
| @return The length of the full format specifier string, starting at | |
| pFormat->ulSpecifierIdx. Returns zero if a valid specifier was | |
| not found. | |
| */ | |
| static uint32_t ParseFormatSpecifier( | |
| char const *pszFomat, | |
| PRINTFORMAT *pFormatType) | |
| { | |
| bool fContainsIllegalSequence = false; | |
| uint32_t ulLen = 0U; | |
| uint32_t ulIdx = 0U; | |
| while(pszFomat[ulIdx] != '\0') | |
| { | |
| uint32_t ulTypeLen; | |
| /* general output | |
| */ | |
| if(pszFomat[ulIdx] != '%') | |
| { | |
| ulIdx++; | |
| } | |
| else | |
| { | |
| RedMemSet(pFormatType, 0U, sizeof(*pFormatType)); | |
| /* Record the location of the start of the format sequence | |
| */ | |
| pFormatType->ulSpecifierIdx = ulIdx; | |
| ulIdx++; | |
| if(pszFomat[ulIdx] == '-') | |
| { | |
| pFormatType->fLeftJustified = true; | |
| ulIdx++; | |
| } | |
| if((pszFomat[ulIdx] == '0') || (pszFomat[ulIdx] == '_')) | |
| { | |
| pFormatType->cFillChar = pszFomat[ulIdx]; | |
| ulIdx++; | |
| } | |
| else | |
| { | |
| pFormatType->cFillChar = ' '; | |
| } | |
| if(pszFomat[ulIdx] == '*') | |
| { | |
| pFormatType->fHasVarWidth = true; | |
| ulIdx++; | |
| } | |
| else if(ISDIGIT(pszFomat[ulIdx])) | |
| { | |
| pFormatType->ulFillLen = (uint32_t)RedAtoI(&pszFomat[ulIdx]); | |
| while(ISDIGIT(pszFomat[ulIdx])) | |
| { | |
| ulIdx++; | |
| } | |
| } | |
| else | |
| { | |
| /* No fill length. | |
| */ | |
| } | |
| pFormatType->type = ParseFormatType(&pszFomat[ulIdx], &ulTypeLen); | |
| if(pFormatType->type != PRFMT_UNKNOWN) | |
| { | |
| /* Even though we are returning successfully, keep track of | |
| whether an illegal sequence was encountered and skipped. | |
| */ | |
| pFormatType->fHasIllegalType = fContainsIllegalSequence; | |
| ulLen = (ulIdx - pFormatType->ulSpecifierIdx) + ulTypeLen; | |
| break; | |
| } | |
| /* In the case of an unrecognized type string, simply ignore | |
| it entirely. Reset the pointer to the position following | |
| the percent sign, so it is not found again. | |
| */ | |
| fContainsIllegalSequence = false; | |
| ulIdx = pFormatType->ulSpecifierIdx + 1U; | |
| } | |
| } | |
| return ulLen; | |
| } | |
| /** @brief Parse a RedPrintf() format type string to determine the proper data | |
| type. | |
| @param pszFormat The format string to process. This must be a pointer to | |
| the character following any width or justification | |
| characters. | |
| @param pulTypeLen The location in which to store the type length. The | |
| value will be 0 if PRFMT_UNKNOWN is returned. | |
| @return Rhe PRFMT_* type value, or PRFMT_UNKNOWN if the type is not | |
| recognized. | |
| */ | |
| static PRINTTYPE ParseFormatType( | |
| const char *pszFormat, | |
| uint32_t *pulTypeLen) | |
| { | |
| PRINTTYPE fmtType = PRFMT_UNKNOWN; | |
| uint32_t ulIdx = 0U; | |
| switch(pszFormat[ulIdx]) | |
| { | |
| case '%': | |
| fmtType = PRFMT_DOUBLEPERCENT; | |
| break; | |
| case 'c': | |
| fmtType = PRFMT_CHAR; | |
| break; | |
| case 's': | |
| fmtType = PRFMT_ANSISTRING; | |
| break; | |
| case 'p': | |
| fmtType = PRFMT_POINTER; | |
| break; | |
| case 'd': | |
| fmtType = MAPINT; | |
| break; | |
| case 'u': | |
| fmtType = MAPUINT; | |
| break; | |
| case 'x': | |
| fmtType = MAPHEXUINT; | |
| break; | |
| case 'h': | |
| { | |
| ulIdx++; | |
| switch(pszFormat[ulIdx]) | |
| { | |
| case 'd': | |
| fmtType = MAPSHORT; | |
| break; | |
| case 'u': | |
| fmtType = MAPUSHORT; | |
| break; | |
| case 'x': | |
| fmtType = MAPHEXUSHORT; | |
| break; | |
| default: | |
| break; | |
| } | |
| break; | |
| } | |
| case 'l': | |
| { | |
| ulIdx++; | |
| switch(pszFormat[ulIdx]) | |
| { | |
| case 'd': | |
| fmtType = MAPLONG; | |
| break; | |
| case 'u': | |
| fmtType = MAPULONG; | |
| break; | |
| case 'x': | |
| fmtType = MAPHEXULONG; | |
| break; | |
| case 'l': | |
| { | |
| ulIdx++; | |
| switch(pszFormat[ulIdx]) | |
| { | |
| case 'd': | |
| fmtType = MAPLONGLONG; | |
| break; | |
| case 'u': | |
| fmtType = MAPULONGLONG; | |
| break; | |
| case 'X': | |
| fmtType = MAPHEXULONGLONG; | |
| break; | |
| default: | |
| break; | |
| } | |
| break; | |
| } | |
| default: | |
| break; | |
| } | |
| break; | |
| } | |
| default: | |
| break; | |
| } | |
| if(fmtType != PRFMT_UNKNOWN) | |
| { | |
| *pulTypeLen = ulIdx + 1U; | |
| } | |
| else | |
| { | |
| *pulTypeLen = 0U; | |
| } | |
| return fmtType; | |
| } | |
| /** @brief Format a signed 32-bit integer as a base 10 ASCII string. | |
| @note If the output buffer length is exhausted, the result will *not* be | |
| null-terminated. | |
| @note If the @p ulFillLen value is greater than or equal to the buffer | |
| length, the result will not be null-terminated, even if the | |
| formatted portion of the data is shorter than the buffer length. | |
| @param pcBuffer The output buffer | |
| @param ulBufferLen A pointer to the output buffer length | |
| @param lNum The 32-bit signed number to convert | |
| @param ulFillLen The fill length, if any | |
| @param cFill The fill character to use | |
| @return The length of the string. | |
| */ | |
| static uint32_t LtoA( | |
| char *pcBuffer, | |
| uint32_t ulBufferLen, | |
| int32_t lNum, | |
| uint32_t ulFillLen, | |
| char cFill) | |
| { | |
| uint32_t ulLen; | |
| if(pcBuffer == NULL) | |
| { | |
| REDERROR(); | |
| ulLen = 0U; | |
| } | |
| else | |
| { | |
| char ach[12U]; /* big enough for a int32_t in base 10 */ | |
| uint32_t ulDigits = 0U; | |
| uint32_t ulNum; | |
| bool fSign; | |
| if(lNum < 0) | |
| { | |
| fSign = true; | |
| ulNum = (uint32_t)-lNum; | |
| } | |
| else | |
| { | |
| fSign = false; | |
| ulNum = (uint32_t)lNum; | |
| } | |
| do | |
| { | |
| ach[ulDigits] = gacDigits[ulNum % 10U]; | |
| ulNum = ulNum / 10U; | |
| ulDigits++; | |
| } | |
| while(ulNum); | |
| if(fSign) | |
| { | |
| ach[ulDigits] = '-'; | |
| ulDigits++; | |
| } | |
| ulLen = FinishToA(ach, ulDigits, pcBuffer, ulBufferLen, ulFillLen, cFill); | |
| } | |
| return ulLen; | |
| } | |
| /** @brief Format a signed 64-bit integer as a base 10 ASCII string. | |
| @note If the output buffer length is exhausted, the result will *not* be | |
| null-terminated. | |
| @note If the @p ulFillLen value is greater than or equal to the buffer | |
| length, the result will not be null-terminated, even if the | |
| formatted portion of the data is shorter than the buffer length. | |
| @param pcBuffer The output buffer | |
| @param ulBufferLen A pointer to the output buffer length | |
| @param llNum The 64-bit signed number to convert | |
| @param ulFillLen The fill length, if any | |
| @param cFill The fill character to use | |
| @return The length of the string. | |
| */ | |
| static uint32_t LLtoA( | |
| char *pcBuffer, | |
| uint32_t ulBufferLen, | |
| int64_t llNum, | |
| uint32_t ulFillLen, | |
| char cFill) | |
| { | |
| uint32_t ulLen; | |
| if(pcBuffer == NULL) | |
| { | |
| REDERROR(); | |
| ulLen = 0U; | |
| } | |
| else | |
| { | |
| char ach[12U]; /* big enough for a int32_t in base 10 */ | |
| uint32_t ulDigits = 0U; | |
| uint64_t ullNum; | |
| bool fSign; | |
| if(llNum < 0) | |
| { | |
| fSign = true; | |
| ullNum = (uint64_t)-llNum; | |
| } | |
| else | |
| { | |
| fSign = false; | |
| ullNum = (uint64_t)llNum; | |
| } | |
| /* Not allowed to assume that 64-bit division is OK, so use a | |
| software division routine. | |
| */ | |
| do | |
| { | |
| uint64_t ullQuotient; | |
| uint32_t ulRemainder; | |
| /* Note: RedUint64DivMod32() is smart enough to use normal division | |
| once ullNumericVal <= UINT32_MAX. | |
| */ | |
| ullQuotient = RedUint64DivMod32(ullNum, 10U, &ulRemainder); | |
| ach[ulDigits] = gacDigits[ulRemainder]; | |
| ullNum = ullQuotient; | |
| ulDigits++; | |
| } | |
| while(ullNum > 0U); | |
| if(fSign) | |
| { | |
| ach[ulDigits] = '-'; | |
| ulDigits++; | |
| } | |
| ulLen = FinishToA(ach, ulDigits, pcBuffer, ulBufferLen, ulFillLen, cFill); | |
| } | |
| return ulLen; | |
| } | |
| /** @brief Format an unsigned 32-bit integer as an ASCII string as decimal or | |
| hex. | |
| @note If the output buffer length is exhausted, the result will *not* be | |
| null-terminated. | |
| @param pcBuffer The output buffer | |
| @param ulBufferLen The output buffer length | |
| @param ulNum The 32-bit unsigned number to convert | |
| @param fHex If true, format as hex; if false, decimal. | |
| @param ulFillLen The fill length, if any | |
| @param cFill The fill character to use | |
| @return The length of the string. | |
| */ | |
| static uint32_t ULtoA( | |
| char *pcBuffer, | |
| uint32_t ulBufferLen, | |
| uint32_t ulNum, | |
| bool fHex, | |
| uint32_t ulFillLen, | |
| char cFill) | |
| { | |
| uint32_t ulLen; | |
| if(pcBuffer == NULL) | |
| { | |
| REDERROR(); | |
| ulLen = 0U; | |
| } | |
| else | |
| { | |
| char ach[11U]; /* Big enough for a uint32_t in radix 10 */ | |
| uint32_t ulDigits = 0U; | |
| uint32_t ulNumericVal = ulNum; | |
| uint32_t ulRadix = fHex ? 16U : 10U; | |
| do | |
| { | |
| ach[ulDigits] = gacDigits[ulNumericVal % ulRadix]; | |
| ulNumericVal = ulNumericVal / ulRadix; | |
| ulDigits++; | |
| } | |
| while(ulNumericVal > 0U); | |
| ulLen = FinishToA(ach, ulDigits, pcBuffer, ulBufferLen, ulFillLen, cFill); | |
| } | |
| return ulLen; | |
| } | |
| /** @brief Format an unsigned 64-bit integer as an ASCII string as decimal or | |
| hex. | |
| @note If the output buffer length is exhausted, the result will *not* be | |
| null-terminated. | |
| @param pcBuffer The output buffer. | |
| @param ulBufferLen The output buffer length. | |
| @param ullNum The unsigned 64-bit number to convert. | |
| @param fHex If true, format as hex; if false, decimal. | |
| @param ulFillLen The fill length, if any. | |
| @param cFill The fill character to use. | |
| @return The length of the string. | |
| */ | |
| static uint32_t ULLtoA( | |
| char *pcBuffer, | |
| uint32_t ulBufferLen, | |
| uint64_t ullNum, | |
| bool fHex, | |
| uint32_t ulFillLen, | |
| char cFill) | |
| { | |
| uint32_t ulLen; | |
| if(pcBuffer == NULL) | |
| { | |
| REDERROR(); | |
| ulLen = 0U; | |
| } | |
| else | |
| { | |
| char ach[21U]; /* Big enough for a uint64_t in radix 10 */ | |
| uint32_t ulDigits = 0U; | |
| uint64_t ullNumericVal = ullNum; | |
| if(fHex) | |
| { | |
| /* We can figure out the digits using bit operations. | |
| */ | |
| do | |
| { | |
| ach[ulDigits] = gacDigits[ullNumericVal & 15U]; | |
| ullNumericVal >>= 4U; | |
| ulDigits++; | |
| } | |
| while(ullNumericVal > 0U); | |
| } | |
| else | |
| { | |
| /* Not allowed to assume that 64-bit division is OK, so use a | |
| software division routine. | |
| */ | |
| do | |
| { | |
| uint64_t ullQuotient; | |
| uint32_t ulRemainder; | |
| /* Note: RedUint64DivMod32() is smart enough to use normal division | |
| once ullNumericVal <= UINT32_MAX. | |
| */ | |
| ullQuotient = RedUint64DivMod32(ullNumericVal, 10U, &ulRemainder); | |
| ach[ulDigits] = gacDigits[ulRemainder]; | |
| ullNumericVal = ullQuotient; | |
| ulDigits++; | |
| } | |
| while(ullNumericVal > 0U); | |
| } | |
| ulLen = FinishToA(ach, ulDigits, pcBuffer, ulBufferLen, ulFillLen, cFill); | |
| } | |
| return ulLen; | |
| } | |
| /** @brief Finish converting a number into an ASCII string representing that | |
| number. | |
| This helper function contains common logic that needs to run at the end of | |
| all the "toA" functions. It adds the fill character and reverses the digits | |
| string. | |
| @param pcDigits The digits (and sign) for the ASCII string, in reverse | |
| order as they were computed. | |
| @param ulDigits The number of digit characters. | |
| @param pcOutBuffer The output buffer. | |
| @param ulBufferLen The length of the output buffer. | |
| @param ulFillLen The fill length. If the number string is shorter than | |
| this, the remaining bytes are filled with @p cFill. | |
| @param cFill The fill character. | |
| @return The length of @p pcOutBuffer. | |
| */ | |
| static uint32_t FinishToA( | |
| const char *pcDigits, | |
| uint32_t ulDigits, | |
| char *pcOutBuffer, | |
| uint32_t ulBufferLen, | |
| uint32_t ulFillLen, | |
| char cFill) | |
| { | |
| uint32_t ulIdx = 0U; | |
| uint32_t ulDigitIdx = ulDigits; | |
| /* user may have asked for a fill char | |
| */ | |
| if(ulFillLen > ulDigits) | |
| { | |
| uint32_t ulFillRem = ulFillLen - ulDigits; | |
| while((ulFillRem > 0U) && (ulIdx < ulBufferLen)) | |
| { | |
| pcOutBuffer[ulIdx] = cFill; | |
| ulIdx++; | |
| ulFillRem--; | |
| } | |
| } | |
| /* reverse the string | |
| */ | |
| while((ulDigitIdx > 0) && (ulIdx < ulBufferLen)) | |
| { | |
| ulDigitIdx--; | |
| pcOutBuffer[ulIdx] = pcDigits[ulDigitIdx]; | |
| ulIdx++; | |
| } | |
| if(ulIdx < ulBufferLen) | |
| { | |
| pcOutBuffer[ulIdx] = '\0'; | |
| } | |
| return ulIdx; | |
| } | |