diff --git a/guides/Documentation.htm b/guides/Documentation.htm index 2b6497ba..1441e098 100644 --- a/guides/Documentation.htm +++ b/guides/Documentation.htm @@ -1210,6 +1210,7 @@

Stream

String

Class methods:

+

+

String.Format

+ String.Format(string[, values]) +
Formats a format string according to the given format specifiers. A format specifier is a string of the form

%[flags][width][.precision][conversion specifier]

where a conversion specifier must be one of the following:
Flags are optional, and must be one of the following:
A width sub-specifier is used in conjunction with the flags:
Precision specifiers are also supported:
+
Parameters:
+ +
Returns:
+
Returns a String value.
+

String.Split

String.Split(string, delimiter) @@ -10011,7 +10024,7 @@

XML.Parse

Returns:
Returns a Map value if the text can be decoded, otherwise returns null.

-

723 out of 782 functions have descriptions.

+

724 out of 783 functions have descriptions.


Instance methods

diff --git a/source/Engine/Bytecode/StandardLibrary.cpp b/source/Engine/Bytecode/StandardLibrary.cpp index 96a0cbab..326caf74 100644 --- a/source/Engine/Bytecode/StandardLibrary.cpp +++ b/source/Engine/Bytecode/StandardLibrary.cpp @@ -57,6 +57,15 @@ class StandardLibrary { #include FT_FREETYPE_H #endif +#define NANOPRINTF_IMPLEMENTATION +#define NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS 1 +#define NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS 1 +#define NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS 1 +#define NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS 1 +#define NANOPRINTF_VISIBILITY_STATIC 1 + +#include + #define THROW_ERROR(...) ScriptManager::Threads[threadID].ThrowRuntimeError(false, __VA_ARGS__) #define CHECK_ARGCOUNT(expects) \ @@ -113,131 +122,117 @@ namespace LOCAL { inline char* GetString(VMValue* args, int index, Uint32 threadID) { char* value = NULL; if (ScriptManager::Lock()) { - if (!IS_STRING(args[index])) { + if (IS_STRING(args[index])) { + value = AS_CSTRING(args[index]); + } else { if (THROW_ERROR( "Expected argument %d to be of type %s instead of %s.", index + 1, GetObjectTypeString(OBJ_STRING), GetValueTypeString(args[index])) == ERROR_RES_CONTINUE) { ScriptManager::Unlock(); ScriptManager::Threads[threadID].ReturnFromNative(); } } - - value = AS_CSTRING(args[index]); ScriptManager::Unlock(); } - if (!value) { - if (THROW_ERROR("Argument %d could not be read as type %s.", index + 1, - "String")) - ScriptManager::Threads[threadID].ReturnFromNative(); + return value; + } + inline ObjString* GetVMString(VMValue* args, int index, Uint32 threadID) { + ObjString* value = NULL; + if (ScriptManager::Lock()) { + if (IS_STRING(args[index])) { + value = AS_STRING(args[index]); + } else { + if (THROW_ERROR( + "Expected argument %d to be of type %s instead of %s.", index + 1, GetObjectTypeString(OBJ_STRING), GetValueTypeString(args[index])) == ERROR_RES_CONTINUE) { + ScriptManager::Unlock(); + ScriptManager::Threads[threadID].ReturnFromNative(); + } + } + ScriptManager::Unlock(); } return value; } inline ObjArray* GetArray(VMValue* args, int index, Uint32 threadID) { ObjArray* value = NULL; if (ScriptManager::Lock()) { - if (!IS_ARRAY(args[index])) { + if (IS_ARRAY(args[index])) { + value = (ObjArray*)(AS_OBJECT(args[index])); + } else { if (THROW_ERROR( "Expected argument %d to be of type %s instead of %s.", index + 1, GetObjectTypeString(OBJ_ARRAY), GetValueTypeString(args[index])) == ERROR_RES_CONTINUE) ScriptManager::Threads[threadID].ReturnFromNative(); } - - value = (ObjArray*)(AS_OBJECT(args[index])); ScriptManager::Unlock(); } - if (!value) { - if (THROW_ERROR("Argument %d could not be read as type %s.", index + 1, - "Array")) - ScriptManager::Threads[threadID].ReturnFromNative(); - } return value; } inline ObjMap* GetMap(VMValue* args, int index, Uint32 threadID) { ObjMap* value = NULL; if (ScriptManager::Lock()) { - if (!IS_MAP(args[index])) + if (IS_MAP(args[index])) { + value = (ObjMap*)(AS_OBJECT(args[index])); + } else { if (THROW_ERROR( "Expected argument %d to be of type %s instead of %s.", index + 1, GetObjectTypeString(OBJ_MAP), GetValueTypeString(args[index])) == ERROR_RES_CONTINUE) ScriptManager::Threads[threadID].ReturnFromNative(); - - value = (ObjMap*)(AS_OBJECT(args[index])); + } ScriptManager::Unlock(); } - if (!value) { - if (THROW_ERROR("Argument %d could not be read as type %s.", index + 1, - "Map")) - ScriptManager::Threads[threadID].ReturnFromNative(); - } return value; } inline ObjBoundMethod* GetBoundMethod(VMValue* args, int index, Uint32 threadID) { ObjBoundMethod* value = NULL; if (ScriptManager::Lock()) { - if (!IS_BOUND_METHOD(args[index])) + if (IS_BOUND_METHOD(args[index])) { + value = (ObjBoundMethod*)(AS_OBJECT(args[index])); + } else { if (THROW_ERROR( "Expected argument %d to be of type %s instead of %s.", index + 1, GetObjectTypeString(OBJ_BOUND_METHOD), GetValueTypeString(args[index])) == ERROR_RES_CONTINUE) ScriptManager::Threads[threadID].ReturnFromNative(); - - value = (ObjBoundMethod*)(AS_OBJECT(args[index])); + } ScriptManager::Unlock(); } - if (!value) { - if (THROW_ERROR("Argument %d could not be read as type %s.", index + 1, - "Event")) - ScriptManager::Threads[threadID].ReturnFromNative(); - } return value; } inline ObjFunction* GetFunction(VMValue* args, int index, Uint32 threadID) { ObjFunction* value = NULL; if (ScriptManager::Lock()) { - if (!IS_FUNCTION(args[index])) + if (IS_FUNCTION(args[index])) { + value = (ObjFunction*)(AS_OBJECT(args[index])); + } else { if (THROW_ERROR( "Expected argument %d to be of type %s instead of %s.", index + 1, GetObjectTypeString(OBJ_FUNCTION), GetValueTypeString(args[index])) == ERROR_RES_CONTINUE) ScriptManager::Threads[threadID].ReturnFromNative(); - - value = (ObjFunction*)(AS_OBJECT(args[index])); + } ScriptManager::Unlock(); } - if (!value) { - if (THROW_ERROR("Argument %d could not be read as type %s.", index + 1, - "Event")) - ScriptManager::Threads[threadID].ReturnFromNative(); - } return value; } inline ObjInstance* GetInstance(VMValue* args, int index, Uint32 threadID) { ObjInstance* value = NULL; if (ScriptManager::Lock()) { - if (!IS_INSTANCE(args[index])) + if (IS_INSTANCE(args[index])) { + value = (ObjInstance*)(AS_OBJECT(args[index])); + } else { if (THROW_ERROR( "Expected argument %d to be of type %s instead of %s.", index + 1, GetObjectTypeString(OBJ_INSTANCE), GetValueTypeString(args[index])) == ERROR_RES_CONTINUE) ScriptManager::Threads[threadID].ReturnFromNative(); - - value = (ObjInstance*)(AS_OBJECT(args[index])); + } ScriptManager::Unlock(); } - if (!value) { - if (THROW_ERROR("Argument %d could not be read as type %s.", index + 1, - "Instance")) - ScriptManager::Threads[threadID].ReturnFromNative(); - } return value; } inline ObjStream* GetStream(VMValue* args, int index, Uint32 threadID) { ObjStream* value = NULL; if (ScriptManager::Lock()) { - if (!IS_STREAM(args[index])) + if (IS_STREAM(args[index])) { + value = (ObjStream*)(AS_OBJECT(args[index])); + } else { if (THROW_ERROR( "Expected argument %d to be of type %s instead of %s.", index + 1, GetObjectTypeString(OBJ_STREAM), GetValueTypeString(args[index])) == ERROR_RES_CONTINUE) ScriptManager::Threads[threadID].ReturnFromNative(); - - value = (ObjStream*)(AS_OBJECT(args[index])); + } ScriptManager::Unlock(); } - if (!value) { - if (THROW_ERROR("Argument %d could not be read as type %s.", index + 1, - "Stream")) - ScriptManager::Threads[threadID].ReturnFromNative(); - } return value; } @@ -370,6 +365,9 @@ PUBLIC STATIC float StandardLibrary::GetDecimal(VMValue* args, int index, PUBLIC STATIC char* StandardLibrary::GetString(VMValue* args, int index, Uint32 threadID) { return LOCAL::GetString(args, index, threadID); } +PUBLIC STATIC ObjString* StandardLibrary::GetVMString(VMValue* args, int index, Uint32 threadID) { + return LOCAL::GetVMString(args, index, threadID); +} PUBLIC STATIC ObjArray* StandardLibrary::GetArray(VMValue* args, int index, Uint32 threadID) { return LOCAL::GetArray(args, index, threadID); } @@ -14389,6 +14387,71 @@ VMValue Stream_WriteString(int argCount, VMValue* args, Uint32 threadID) { // #endregion // #region String +/*** + * String.Format + * \desc Formats a format string according to the given format specifiers. A format specifier is a string of the form

\ +%[flags][width][.precision][conversion specifier]

\ +where a conversion specifier must be one of the following:
\ +

\ +Flags are optional, and must be one of the following:
\ +\ +A width sub-specifier is used in conjunction with the flags:
\ +\ +Precision specifiers are also supported:
\ + + * \param string (String): The format string. + * \paramOpt values (any type): Variable arguments. + * \return Returns a String value. + * \ns String + */ +VMValue String_Format(int argCount, VMValue* args, Uint32 threadID) { + CHECK_AT_LEAST_ARGCOUNT(1); + char* fmtString = GET_ARG(0, GetString); + + if (fmtString && ScriptManager::Lock()) { + ObjString* newString; + if (argCount > 1) { + newString = npf_vpprintf(fmtString, argCount - 1, args + 1, threadID); + } + else { + newString = CopyString(fmtString); + } + ScriptManager::Unlock(); + return OBJECT_VAL(newString); + } + + return NULL_VAL; +} /*** * String.Split * \desc Splits a string by a delimiter. @@ -18090,6 +18153,7 @@ PUBLIC STATIC void StandardLibrary::Link() { // #region String INIT_CLASS(String); + DEF_NATIVE(String, Format); DEF_NATIVE(String, Split); DEF_NATIVE(String, CharAt); DEF_NATIVE(String, Length); diff --git a/source/Engine/Includes/PrintBuffer.h b/source/Engine/Includes/PrintBuffer.h index 5f740182..c4759628 100644 --- a/source/Engine/Includes/PrintBuffer.h +++ b/source/Engine/Includes/PrintBuffer.h @@ -45,4 +45,51 @@ inline int buffer_printf(PrintBuffer* printBuffer, const char *format, ...) { return 0; } +inline int buffer_write(PrintBuffer* printBuffer, const char *string) { + if (!printBuffer || !printBuffer->Buffer) + return 0; + + int count = strlen(string); + + while (printBuffer->WriteIndex + count >= printBuffer->BufferSize) { + // Increase the buffer size + printBuffer->BufferSize <<= 1; + + // Reallocate buffer + *printBuffer->Buffer = (char*)realloc(*printBuffer->Buffer, printBuffer->BufferSize); + if (!*printBuffer->Buffer) { + Log::Print(Log::LOG_ERROR, "Could not reallocate print buffer of size %d!", printBuffer->BufferSize); + return -1; + } + } + + // Copy the string + strcpy(&((*printBuffer->Buffer)[printBuffer->WriteIndex]), string); + printBuffer->WriteIndex += count; + + return count; +} + +inline int buffer_write(PrintBuffer* printBuffer, char chr) { + if (!printBuffer || !printBuffer->Buffer) + return 0; + + while (printBuffer->WriteIndex + 1 >= printBuffer->BufferSize) { + // Increase the buffer size + printBuffer->BufferSize <<= 1; + + // Reallocate buffer + *printBuffer->Buffer = (char*)realloc(*printBuffer->Buffer, printBuffer->BufferSize); + if (!*printBuffer->Buffer) { + Log::Print(Log::LOG_ERROR, "Could not reallocate print buffer of size %d!", printBuffer->BufferSize); + return -1; + } + } + + // Write the character + (*printBuffer->Buffer)[printBuffer->WriteIndex] = chr; + printBuffer->WriteIndex++; + + return 1; +} #endif diff --git a/source/Libraries/nanoprintf.h b/source/Libraries/nanoprintf.h new file mode 100644 index 00000000..25549198 --- /dev/null +++ b/source/Libraries/nanoprintf.h @@ -0,0 +1,949 @@ +/* nanoprintf v0.5.3: a tiny embeddable printf replacement written in C. + https://github.com/charlesnicholson/nanoprintf + charles.nicholson+nanoprintf@gmail.com + dual-licensed under 0bsd and unlicense, take your pick. see eof for details. + + Modified for use in the Hatch Game Engine. + + What was changed: + + 1. Removed NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS and NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS. + Hatch does not need either of these. + 2. As a result of the above, removed '%n' and all large format specifiers. + 3. Removed '%i', '%u', and '%p'. + Same reason as bullet point 1. + 4. Removed '%e'/'%E', '%g'/'%G', and '%a'/'%A'. + They were already unimplemented in nanoprintf as of v0.5.3, and Hatch does not need these. + 5. Removed the functions npf_snprintf, npf_vsnprintf, npf_pprintf, and the npf_putc typedef. + 6. Modified npf_vpprintf to take a list of VMValues, and to return an ObjString. + 7. Removed NANOPRINTF_SNPRINTF_SAFE_EMPTY_STRING_ON_OVERFLOW. */ + +#ifndef NANOPRINTF_H_INCLUDED +#define NANOPRINTF_H_INCLUDED + +#include +#include + +// Define this to fully sandbox nanoprintf inside of a translation unit. +#ifdef NANOPRINTF_VISIBILITY_STATIC + #define NPF_VISIBILITY static +#else + #define NPF_VISIBILITY extern +#endif + +// Public API + +#ifdef __cplusplus +extern "C" { +#endif + +// This function returns an ObjString containing the fully-formatted string. + +NPF_VISIBILITY ObjString* npf_vpprintf(char const *format, int argCount, VMValue* args, Uint32 threadID); + +#ifdef __cplusplus +} +#endif + +#endif // NANOPRINTF_H_INCLUDED + +/* The implementation of nanoprintf begins here, to be compiled only if + NANOPRINTF_IMPLEMENTATION is defined. In a multi-file library what follows would + be nanoprintf.c. */ + +#ifdef NANOPRINTF_IMPLEMENTATION + +#ifndef NANOPRINTF_IMPLEMENTATION_INCLUDED +#define NANOPRINTF_IMPLEMENTATION_INCLUDED + +#include +#include + +// The conversion buffer must fit at least UINT64_MAX in octal format with the leading '0'. +#ifndef NANOPRINTF_CONVERSION_BUFFER_SIZE + #define NANOPRINTF_CONVERSION_BUFFER_SIZE 23 +#endif +#if NANOPRINTF_CONVERSION_BUFFER_SIZE < 23 + #error The size of the conversion buffer must be at least 23 bytes. +#endif + +// Pick reasonable defaults if nothing's been configured. +#if !defined(NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS) && \ + !defined(NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS) && \ + !defined(NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS) && \ + !defined(NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS) + #define NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS 1 + #define NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS 1 + #define NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS 1 + #define NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS 0 +#endif + +// If anything's been configured, everything must be configured. +#ifndef NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS + #error NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS must be #defined to 0 or 1 +#endif +#ifndef NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS + #error NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS must be #defined to 0 or 1 +#endif +#ifndef NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS + #error NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS must be #defined to 0 or 1 +#endif +#ifndef NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS + #error NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS must be #defined to 0 or 1 +#endif + +// Ensure flags are compatible. +#if (NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1) && \ + (NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 0) + #error Precision format specifiers must be enabled if float support is enabled. +#endif + +// Figure out if we can disable warnings with pragmas. +#ifdef __clang__ + #define NANOPRINTF_CLANG 1 + #define NANOPRINTF_GCC_PAST_4_6 0 +#else + #define NANOPRINTF_CLANG 0 + #if defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 6))) + #define NANOPRINTF_GCC_PAST_4_6 1 + #else + #define NANOPRINTF_GCC_PAST_4_6 0 + #endif +#endif + +#if NANOPRINTF_CLANG || NANOPRINTF_GCC_PAST_4_6 + #define NANOPRINTF_HAVE_GCC_WARNING_PRAGMAS 1 +#else + #define NANOPRINTF_HAVE_GCC_WARNING_PRAGMAS 0 +#endif + +#if NANOPRINTF_HAVE_GCC_WARNING_PRAGMAS + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wunused-function" + #pragma GCC diagnostic ignored "-Wimplicit-fallthrough" + #ifdef __cplusplus + #pragma GCC diagnostic ignored "-Wold-style-cast" + #endif + #pragma GCC diagnostic ignored "-Wpadded" + #pragma GCC diagnostic ignored "-Wfloat-equal" + #if NANOPRINTF_CLANG + #pragma GCC diagnostic ignored "-Wc++98-compat-pedantic" + #pragma GCC diagnostic ignored "-Wcovered-switch-default" + #pragma GCC diagnostic ignored "-Wdeclaration-after-statement" + #pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant" + #ifndef __APPLE__ + #pragma GCC diagnostic ignored "-Wunsafe-buffer-usage" + #endif + #elif NANOPRINTF_GCC_PAST_4_6 + #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" + #endif +#endif + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable:4619) // there is no warning number 'number' + // C4619 has to be disabled first! + #pragma warning(disable:4127) // conditional expression is constant + #pragma warning(disable:4505) // unreferenced local function has been removed + #pragma warning(disable:4514) // unreferenced inline function has been removed + #pragma warning(disable:4701) // potentially uninitialized local variable used + #pragma warning(disable:4706) // assignment within conditional expression + #pragma warning(disable:4710) // function not inlined + #pragma warning(disable:4711) // function selected for inline expansion + #pragma warning(disable:4820) // padding added after struct member + #pragma warning(disable:5039) // potentially throwing function passed to extern C function + #pragma warning(disable:5045) // compiler will insert Spectre mitigation for memory load + #pragma warning(disable:5262) // implicit switch fall-through + #pragma warning(disable:26812) // enum type is unscoped +#endif + +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #define NPF_NOINLINE __attribute__((noinline)) +#elif defined(_MSC_VER) + #define NPF_NOINLINE __declspec(noinline) +#else + #define NPF_NOINLINE +#endif + +#if (NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1) || \ + (NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1) +enum { + NPF_FMT_SPEC_OPT_NONE, + NPF_FMT_SPEC_OPT_LITERAL, + NPF_FMT_SPEC_OPT_STAR, +}; +#endif + +enum { + NPF_FMT_SPEC_CONV_NONE, + NPF_FMT_SPEC_CONV_PERCENT, // '%' + NPF_FMT_SPEC_CONV_CHAR, // 'c' + NPF_FMT_SPEC_CONV_STRING, // 's' + NPF_FMT_SPEC_CONV_SIGNED_INT, // 'i', 'd' +#if NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS == 1 + NPF_FMT_SPEC_CONV_BINARY, // 'b' +#endif + NPF_FMT_SPEC_CONV_OCTAL, // 'o' + NPF_FMT_SPEC_CONV_HEX_INT, // 'x', 'X' +#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1 + NPF_FMT_SPEC_CONV_FLOAT_DEC, // 'f', 'F' +#endif +}; + +typedef struct npf_format_spec { +#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1 + int field_width; + uint8_t field_width_opt; + char left_justified; // '-' + char leading_zero_pad; // '0' +#endif +#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1 + int prec; + uint8_t prec_opt; +#endif + char prepend; // ' ' or '+' + char alt_form; // '#' + char case_adjust; // 'a' - 'A' + uint8_t conv_spec; +} npf_format_spec_t; + +typedef long npf_int_t; +typedef unsigned long npf_uint_t; + +#ifdef _MSC_VER + #include +#endif + +static int npf_max(int x, int y) { return (x > y) ? x : y; } + +static int npf_parse_format_spec(char const *format, npf_format_spec_t *out_spec) { + char const *cur = format; + +#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1 + out_spec->left_justified = 0; + out_spec->leading_zero_pad = 0; +#endif + out_spec->case_adjust = 'a' - 'A'; // lowercase + out_spec->prepend = 0; + out_spec->alt_form = 0; + + while (*++cur) { // cur points at the leading '%' character + switch (*cur) { // Optional flags +#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1 + case '-': out_spec->left_justified = '-'; out_spec->leading_zero_pad = 0; continue; + case '0': out_spec->leading_zero_pad = !out_spec->left_justified; continue; +#endif + case '+': out_spec->prepend = '+'; continue; + case ' ': if (out_spec->prepend == 0) { out_spec->prepend = ' '; } continue; + case '#': out_spec->alt_form = '#'; continue; + default: break; + } + break; + } + +#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1 + out_spec->field_width_opt = NPF_FMT_SPEC_OPT_NONE; + if (*cur == '*') { + out_spec->field_width_opt = NPF_FMT_SPEC_OPT_STAR; + ++cur; + } else { + out_spec->field_width = 0; + while ((*cur >= '0') && (*cur <= '9')) { + out_spec->field_width_opt = NPF_FMT_SPEC_OPT_LITERAL; + out_spec->field_width = (out_spec->field_width * 10) + (*cur++ - '0'); + } + } +#endif + +#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1 + out_spec->prec = 0; + out_spec->prec_opt = NPF_FMT_SPEC_OPT_NONE; + if (*cur == '.') { + ++cur; + if (*cur == '*') { + out_spec->prec_opt = NPF_FMT_SPEC_OPT_STAR; + ++cur; + } else { + if (*cur == '-') { + ++cur; + } else { + out_spec->prec_opt = NPF_FMT_SPEC_OPT_LITERAL; + } + while ((*cur >= '0') && (*cur <= '9')) { + out_spec->prec = (out_spec->prec * 10) + (*cur++ - '0'); + } + } + } +#endif + + uint_fast8_t tmp_conv = NPF_FMT_SPEC_CONV_NONE; + + switch (*cur++) { // Conversion specifier + case '%': out_spec->conv_spec = NPF_FMT_SPEC_CONV_PERCENT; +#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1 + out_spec->prec_opt = NPF_FMT_SPEC_OPT_NONE; +#endif + break; + + case 'c': out_spec->conv_spec = NPF_FMT_SPEC_CONV_CHAR; +#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1 + out_spec->prec_opt = NPF_FMT_SPEC_OPT_NONE; +#endif + break; + + case 's': out_spec->conv_spec = NPF_FMT_SPEC_CONV_STRING; +#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1 + out_spec->leading_zero_pad = 0; +#endif + break; + + case 'd': tmp_conv = NPF_FMT_SPEC_CONV_SIGNED_INT; + case 'o': + if (tmp_conv == NPF_FMT_SPEC_CONV_NONE) { tmp_conv = NPF_FMT_SPEC_CONV_OCTAL; } + case 'X': + if (tmp_conv == NPF_FMT_SPEC_CONV_NONE) { out_spec->case_adjust = 0; } + case 'x': + if (tmp_conv == NPF_FMT_SPEC_CONV_NONE) { tmp_conv = NPF_FMT_SPEC_CONV_HEX_INT; } + out_spec->conv_spec = (uint8_t)tmp_conv; +#if (NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1) && \ + (NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1) + if (out_spec->prec_opt != NPF_FMT_SPEC_OPT_NONE) { out_spec->leading_zero_pad = 0; } +#endif + break; + +#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1 + case 'F': out_spec->case_adjust = 0; + case 'f': + out_spec->conv_spec = NPF_FMT_SPEC_CONV_FLOAT_DEC; + if (out_spec->prec_opt == NPF_FMT_SPEC_OPT_NONE) { out_spec->prec = 6; } + break; +#endif + +#if NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS == 1 + case 'B': + out_spec->case_adjust = 0; + case 'b': + out_spec->conv_spec = NPF_FMT_SPEC_CONV_BINARY; + break; +#endif + + default: return 0; + } + + return (int)(cur - format); +} + +static NPF_NOINLINE int npf_utoa_rev( + npf_uint_t val, char *buf, uint_fast8_t base, char case_adj) { + uint_fast8_t n = 0; + do { + int_fast8_t const d = (int_fast8_t)(val % base); + *buf++ = (char)(((d < 10) ? '0' : ('A' - 10 + case_adj)) + d); + ++n; + val /= base; + } while (val); + return (int)n; +} + +#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1 + +#include + +#if (DBL_MANT_DIG <= 11) && (DBL_MAX_EXP <= 16) + typedef uint_fast16_t npf_double_bin_t; + typedef int_fast8_t npf_ftoa_exp_t; +#elif (DBL_MANT_DIG <= 24) && (DBL_MAX_EXP <= 128) + typedef uint_fast32_t npf_double_bin_t; + typedef int_fast8_t npf_ftoa_exp_t; +#elif (DBL_MANT_DIG <= 53) && (DBL_MAX_EXP <= 1024) + typedef uint_fast64_t npf_double_bin_t; + typedef int_fast16_t npf_ftoa_exp_t; +#else + #error Unsupported width of the double type. +#endif + +// The floating point conversion code works with an unsigned integer type of any size. +#ifndef NANOPRINTF_CONVERSION_FLOAT_TYPE + #define NANOPRINTF_CONVERSION_FLOAT_TYPE unsigned int +#endif +typedef NANOPRINTF_CONVERSION_FLOAT_TYPE npf_ftoa_man_t; + +#if (NANOPRINTF_CONVERSION_BUFFER_SIZE <= UINT_FAST8_MAX) && (UINT_FAST8_MAX <= INT_MAX) + typedef uint_fast8_t npf_ftoa_dec_t; +#else + typedef int npf_ftoa_dec_t; +#endif + +enum { + NPF_DOUBLE_EXP_MASK = DBL_MAX_EXP * 2 - 1, + NPF_DOUBLE_EXP_BIAS = DBL_MAX_EXP - 1, + NPF_DOUBLE_MAN_BITS = DBL_MANT_DIG - 1, + NPF_DOUBLE_BIN_BITS = sizeof(npf_double_bin_t) * CHAR_BIT, + NPF_FTOA_MAN_BITS = sizeof(npf_ftoa_man_t) * CHAR_BIT, + NPF_FTOA_SHIFT_BITS = + ((NPF_FTOA_MAN_BITS < DBL_MANT_DIG) ? NPF_FTOA_MAN_BITS : DBL_MANT_DIG) - 1 +}; + +/* Generally, floating-point conversion implementations use + grisu2 (https://bit.ly/2JgMggX) and ryu (https://bit.ly/2RLXSg0) algorithms, + which are mathematically exact and fast, but require large lookup tables. + + This implementation was inspired by Wojciech Muła's (zdjęcia@garnek.pl) + algorithm (http://0x80.pl/notesen/2015-12-29-float-to-string.html) and + extended further by adding dynamic scaling and configurable integer width by + Oskars Rubenis (https://github.com/Okarss). */ + +static int npf_ftoa_rev(char *buf, npf_format_spec_t const *spec, double f) { + char const *ret = NULL; + npf_double_bin_t bin; { // Union-cast is UB pre-C11, compiler optimizes byte-copy loop. + char const *src = (char const *)&f; + char *dst = (char *)&bin; + for (uint_fast8_t i = 0; i < sizeof(f); ++i) { dst[i] = src[i]; } + } + + // Unsigned -> signed int casting is IB and can raise a signal but generally doesn't. + npf_ftoa_exp_t exp = + (npf_ftoa_exp_t)((npf_ftoa_exp_t)(bin >> NPF_DOUBLE_MAN_BITS) & NPF_DOUBLE_EXP_MASK); + + bin &= ((npf_double_bin_t)0x1 << NPF_DOUBLE_MAN_BITS) - 1; + if (exp == (npf_ftoa_exp_t)NPF_DOUBLE_EXP_MASK) { // special value + ret = (bin) ? "NAN" : "FNI"; + goto exit; + } + if (spec->prec > (NANOPRINTF_CONVERSION_BUFFER_SIZE - 2)) { goto exit; } + if (exp) { // normal number + bin |= (npf_double_bin_t)0x1 << NPF_DOUBLE_MAN_BITS; + } else { // subnormal number + ++exp; + } + exp = (npf_ftoa_exp_t)(exp - NPF_DOUBLE_EXP_BIAS); + + uint_fast8_t carry; carry = 0; + npf_ftoa_dec_t end, dec; dec = (npf_ftoa_dec_t)spec->prec; + if (dec || spec->alt_form) { + buf[dec++] = '.'; + } + + { // Integer part + npf_ftoa_man_t man_i; + + if (exp >= 0) { + int_fast8_t shift_i = + (int_fast8_t)((exp > NPF_FTOA_SHIFT_BITS) ? (int)NPF_FTOA_SHIFT_BITS : exp); + npf_ftoa_exp_t exp_i = (npf_ftoa_exp_t)(exp - shift_i); + shift_i = (int_fast8_t)(NPF_DOUBLE_MAN_BITS - shift_i); + man_i = (npf_ftoa_man_t)(bin >> shift_i); + + if (exp_i) { + if (shift_i) { + carry = (bin >> (shift_i - 1)) & 0x1; + } + exp = NPF_DOUBLE_MAN_BITS; // invalidate the fraction part + } + + // Scale the exponent from base-2 to base-10. + for (; exp_i; --exp_i) { + if (!(man_i & ((npf_ftoa_man_t)0x1 << (NPF_FTOA_MAN_BITS - 1)))) { + man_i = (npf_ftoa_man_t)(man_i << 1); + man_i = (npf_ftoa_man_t)(man_i | carry); carry = 0; + } else { + if (dec >= NANOPRINTF_CONVERSION_BUFFER_SIZE) { goto exit; } + buf[dec++] = '0'; + carry = (((uint_fast8_t)(man_i % 5) + carry) > 2); + man_i /= 5; + } + } + } else { + man_i = 0; + } + end = dec; + + do { // Print the integer + if (end >= NANOPRINTF_CONVERSION_BUFFER_SIZE) { goto exit; } + buf[end++] = (char)('0' + (char)(man_i % 10)); + man_i /= 10; + } while (man_i); + } + + { // Fraction part + npf_ftoa_man_t man_f; + npf_ftoa_dec_t dec_f = (npf_ftoa_dec_t)spec->prec; + + if (exp < NPF_DOUBLE_MAN_BITS) { + int_fast8_t shift_f = (int_fast8_t)((exp < 0) ? -1 : exp); + npf_ftoa_exp_t exp_f = (npf_ftoa_exp_t)(exp - shift_f); + npf_double_bin_t bin_f = + bin << ((NPF_DOUBLE_BIN_BITS - NPF_DOUBLE_MAN_BITS) + shift_f); + + // This if-else statement can be completely optimized at compile time. + if (NPF_DOUBLE_BIN_BITS > NPF_FTOA_MAN_BITS) { + man_f = (npf_ftoa_man_t)(bin_f >> ((unsigned)(NPF_DOUBLE_BIN_BITS - + NPF_FTOA_MAN_BITS) % + NPF_DOUBLE_BIN_BITS)); + carry = (uint_fast8_t)((bin_f >> ((unsigned)(NPF_DOUBLE_BIN_BITS - + NPF_FTOA_MAN_BITS - 1) % + NPF_DOUBLE_BIN_BITS)) & 0x1); + } else { + man_f = (npf_ftoa_man_t)((npf_ftoa_man_t)bin_f + << ((unsigned)(NPF_FTOA_MAN_BITS - + NPF_DOUBLE_BIN_BITS) % NPF_FTOA_MAN_BITS)); + carry = 0; + } + + // Scale the exponent from base-2 to base-10 and prepare the first digit. + for (uint_fast8_t digit = 0; dec_f && (exp_f < 4); ++exp_f) { + if ((man_f > ((npf_ftoa_man_t)-4 / 5)) || digit) { + carry = (uint_fast8_t)(man_f & 0x1); + man_f = (npf_ftoa_man_t)(man_f >> 1); + } else { + man_f = (npf_ftoa_man_t)(man_f * 5); + if (carry) { man_f = (npf_ftoa_man_t)(man_f + 3); carry = 0; } + if (exp_f < 0) { + buf[--dec_f] = '0'; + } else { + ++digit; + } + } + } + man_f = (npf_ftoa_man_t)(man_f + carry); + carry = (exp_f >= 0); + dec = 0; + } else { + man_f = 0; + } + + if (dec_f) { + // Print the fraction + for (;;) { + buf[--dec_f] = (char)('0' + (char)(man_f >> (NPF_FTOA_MAN_BITS - 4))); + man_f = (npf_ftoa_man_t)(man_f & ~((npf_ftoa_man_t)0xF << (NPF_FTOA_MAN_BITS - 4))); + if (!dec_f) { break; } + man_f = (npf_ftoa_man_t)(man_f * 10); + } + man_f = (npf_ftoa_man_t)(man_f << 4); + } + if (exp < NPF_DOUBLE_MAN_BITS) { + carry &= (uint_fast8_t)(man_f >> (NPF_FTOA_MAN_BITS - 1)); + } + } + + // Round the number + for (; carry; ++dec) { + if (dec >= NANOPRINTF_CONVERSION_BUFFER_SIZE) { goto exit; } + if (dec >= end) { buf[end++] = '0'; } + if (buf[dec] == '.') { continue; } + carry = (buf[dec] == '9'); + buf[dec] = (char)(carry ? '0' : (buf[dec] + 1)); + } + + return (int)end; +exit: + if (!ret) { ret = "RRE"; } + uint_fast8_t i; + for (i = 0; ret[i]; ++i) { buf[i] = (char)(ret[i] + spec->case_adjust); } + return (int)i; +} + +#endif // NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS + +#if NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS == 1 +static int npf_bin_len(npf_uint_t u) { + // Return the length of the binary string format of 'u', preferring intrinsics. + if (!u) { return 1; } + +#ifdef _MSC_VER // Win64, use _BSR64 for everything. If x86, use _BSR when non-large. + #define NPF_HAVE_BUILTIN_CLZ + #ifdef _M_X64 + #define NPF_CLZ _BitScanReverse64 + #else + #define NPF_CLZ _BitScanReverse + #endif + #ifdef NPF_HAVE_BUILTIN_CLZ + unsigned long idx; + NPF_CLZ(&idx, u); + return (int)(idx + 1); + #endif +#elif NANOPRINTF_CLANG || NANOPRINTF_GCC_PAST_4_6 + #define NPF_HAVE_BUILTIN_CLZ + #define NPF_CLZ(X) ((sizeof(long) * CHAR_BIT) - (size_t)__builtin_clzl(X)) + return (int)NPF_CLZ(u); +#endif + +#ifndef NPF_HAVE_BUILTIN_CLZ + int n; + for (n = 0; u; ++n, u >>= 1); // slow but small software fallback + return n; +#else + #undef NPF_HAVE_BUILTIN_CLZ + #undef NPF_CLZ +#endif +} +#endif + +typedef struct npf_cnt_putc_ctx { + VMValue* args; + int argcount; + int curarg; + Uint32 thread; + PrintBuffer buffer; +} npf_cnt_putc_ctx_t; + +static void npf_putc_cnt(char c, void *ctx) { + npf_cnt_putc_ctx_t *pc_cnt = (npf_cnt_putc_ctx_t *)ctx; + buffer_write(&pc_cnt->buffer, c); +} + +#define NPF_PUTC(VAL) do { npf_putc_cnt((char)(VAL), &pc_cnt); } while (0) + +#define THROW_TOO_FEW_FMT_ERR(threadID) ScriptManager::Threads[threadID].ThrowRuntimeError(false, "Too few arguments for format string!") + +ObjString* npf_getarg_str(void *ctx) { + npf_cnt_putc_ctx_t *pc_cnt = (npf_cnt_putc_ctx_t *)ctx; + int index = pc_cnt->curarg; + pc_cnt->curarg++; + + ObjString* result = NULL; + if (index < pc_cnt->argcount) { + result = StandardLibrary::GetVMString(pc_cnt->args, index, pc_cnt->thread); + } + else { + THROW_TOO_FEW_FMT_ERR(pc_cnt->thread); + } + + return result; +} + +int npf_getarg_int(void *ctx) { + npf_cnt_putc_ctx_t *pc_cnt = (npf_cnt_putc_ctx_t *)ctx; + int index = pc_cnt->curarg; + pc_cnt->curarg++; + + int result = 0; + if (index < pc_cnt->argcount) { + result = StandardLibrary::GetInteger(pc_cnt->args, index, pc_cnt->thread); + } + else { + THROW_TOO_FEW_FMT_ERR(pc_cnt->thread); + } + + return result; +} + +float npf_getarg_float(void *ctx) { + npf_cnt_putc_ctx_t *pc_cnt = (npf_cnt_putc_ctx_t *)ctx; + int index = pc_cnt->curarg; + pc_cnt->curarg++; + + float result = 0.0f; + if (index < pc_cnt->argcount) { + result = StandardLibrary::GetDecimal(pc_cnt->args, index, pc_cnt->thread); + } + else { + THROW_TOO_FEW_FMT_ERR(pc_cnt->thread); + } + + return result; +} + +#undef THROW_TOO_FEW_FMT_ERR + +ObjString* npf_vpprintf(char const *format, int argCount, VMValue* args, Uint32 threadID) { + npf_format_spec_t fs; + char const *cur = format; + npf_cnt_putc_ctx_t pc_cnt; + pc_cnt.args = args; + pc_cnt.curarg = 0; + pc_cnt.argcount = argCount; + pc_cnt.thread = threadID; + + char* textBuffer = (char*)malloc(512); + pc_cnt.buffer.Buffer = &textBuffer; + pc_cnt.buffer.WriteIndex = 0; + pc_cnt.buffer.BufferSize = 512; + + while (*cur) { + int const fs_len = (*cur != '%') ? 0 : npf_parse_format_spec(cur, &fs); + if (!fs_len) { NPF_PUTC(*cur++); continue; } + cur += fs_len; + + // Extract star-args immediately +#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1 + if (fs.field_width_opt == NPF_FMT_SPEC_OPT_STAR) { + fs.field_width = npf_getarg_int(&pc_cnt); + if (fs.field_width < 0) { + fs.field_width = -fs.field_width; + fs.left_justified = 1; + } + } +#endif +#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1 + if (fs.prec_opt == NPF_FMT_SPEC_OPT_STAR) { + fs.prec = npf_getarg_int(&pc_cnt); + if (fs.prec < 0) { fs.prec_opt = NPF_FMT_SPEC_OPT_NONE; } + } +#endif + + union { char cbuf_mem[NANOPRINTF_CONVERSION_BUFFER_SIZE]; npf_uint_t binval; } u; + char *cbuf = u.cbuf_mem, sign_c = 0; + int cbuf_len = 0, need_0x = 0; +#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1 + int field_pad = 0; + char pad_c = 0; +#endif +#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1 + int prec_pad = 0; +#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1 + int zero = 0; +#endif +#endif + + // Extract and convert the argument to string, point cbuf at the text. + switch (fs.conv_spec) { + case NPF_FMT_SPEC_CONV_PERCENT: + *cbuf = '%'; + cbuf_len = 1; + break; + + case NPF_FMT_SPEC_CONV_CHAR: + *cbuf = (char)npf_getarg_int(&pc_cnt); + cbuf_len = 1; + break; + + case NPF_FMT_SPEC_CONV_STRING: { + ObjString *str = npf_getarg_str(&pc_cnt); + if (str == NULL) + break; + cbuf = str->Chars; +#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1 + char *end = cbuf + str->Length; + for (char const *s = str->Chars; + ((fs.prec_opt == NPF_FMT_SPEC_OPT_NONE) || (cbuf_len < fs.prec)) && s < end; + ++s, ++cbuf_len); +#else + cbuf_len = str->Length; +#endif + } break; + + case NPF_FMT_SPEC_CONV_SIGNED_INT: { + npf_int_t val = npf_getarg_int(&pc_cnt); + + sign_c = (val < 0) ? '-' : fs.prepend; + +#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1 +#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1 + zero = !val; +#endif + // special case, if prec and value are 0, skip + if (!val && (fs.prec_opt != NPF_FMT_SPEC_OPT_NONE) && !fs.prec) { + cbuf_len = 0; + } else +#endif + { + npf_uint_t uval = (npf_uint_t)val; + if (val < 0) { uval = 0 - uval; } + cbuf_len = npf_utoa_rev(uval, cbuf, 10, fs.case_adjust); + } + } break; + +#if NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS == 1 + case NPF_FMT_SPEC_CONV_BINARY: +#endif + case NPF_FMT_SPEC_CONV_OCTAL: + case NPF_FMT_SPEC_CONV_HEX_INT: { + npf_int_t val = (unsigned)npf_getarg_int(&pc_cnt); + +#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1 +#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1 + zero = !val; +#endif + if (!val && (fs.prec_opt != NPF_FMT_SPEC_OPT_NONE) && !fs.prec) { + // Zero value and explicitly-requested zero precision means "print nothing". + if ((fs.conv_spec == NPF_FMT_SPEC_CONV_OCTAL) && fs.alt_form) { + fs.prec = 1; // octal special case, print a single '0' + } + } else +#endif +#if NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS == 1 + if (fs.conv_spec == NPF_FMT_SPEC_CONV_BINARY) { + cbuf_len = npf_bin_len(val); u.binval = val; + } else +#endif + { + uint_fast8_t const base = (fs.conv_spec == NPF_FMT_SPEC_CONV_OCTAL) ? + 8u : ((fs.conv_spec == NPF_FMT_SPEC_CONV_HEX_INT) ? 16u : 10u); + cbuf_len = npf_utoa_rev(val, cbuf, base, fs.case_adjust); + } + + if (val && fs.alt_form && (fs.conv_spec == NPF_FMT_SPEC_CONV_OCTAL)) { + cbuf[cbuf_len++] = '0'; // OK to add leading octal '0' immediately. + } + + if (val && fs.alt_form) { // 0x or 0b but can't write it yet. + if (fs.conv_spec == NPF_FMT_SPEC_CONV_HEX_INT) { need_0x = 'X'; } +#if NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS == 1 + else if (fs.conv_spec == NPF_FMT_SPEC_CONV_BINARY) { need_0x = 'B'; } +#endif + if (need_0x) { need_0x += fs.case_adjust; } + } + } break; + +#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1 + case NPF_FMT_SPEC_CONV_FLOAT_DEC: { + double val = npf_getarg_float(&pc_cnt); + + sign_c = (val < 0.) ? '-' : fs.prepend; +#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1 + zero = (val == 0.); +#endif + cbuf_len = npf_ftoa_rev(cbuf, &fs, val); + } break; +#endif + default: break; + } + +#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1 + // Compute the field width pad character + if (fs.field_width_opt != NPF_FMT_SPEC_OPT_NONE) { + if (fs.leading_zero_pad) { // '0' flag is only legal with numeric types + if ((fs.conv_spec != NPF_FMT_SPEC_CONV_STRING) && + (fs.conv_spec != NPF_FMT_SPEC_CONV_CHAR) && + (fs.conv_spec != NPF_FMT_SPEC_CONV_PERCENT)) { +#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1 + if ((fs.prec_opt != NPF_FMT_SPEC_OPT_NONE) && !fs.prec && zero) { + pad_c = ' '; + } else +#endif + { pad_c = '0'; } + } + } else { pad_c = ' '; } + } +#endif + + // Compute the number of bytes to truncate or '0'-pad. +#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1 + if (fs.conv_spec != NPF_FMT_SPEC_CONV_STRING) { +#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1 + // float precision is after the decimal point + if (fs.conv_spec != NPF_FMT_SPEC_CONV_FLOAT_DEC) +#endif + { prec_pad = npf_max(0, fs.prec - cbuf_len); } + } +#endif + +#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1 + // Given the full converted length, how many pad bytes? + field_pad = fs.field_width - cbuf_len - !!sign_c; + if (need_0x) { field_pad -= 2; } +#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1 + field_pad -= prec_pad; +#endif + field_pad = npf_max(0, field_pad); + + // Apply right-justified field width if requested + if (!fs.left_justified && pad_c) { // If leading zeros pad, sign goes first. + if (pad_c == '0') { + if (sign_c) { NPF_PUTC(sign_c); sign_c = 0; } + // Pad byte is '0', write '0x' before '0' pad chars. + if (need_0x) { NPF_PUTC('0'); NPF_PUTC(need_0x); } + } + while (field_pad-- > 0) { NPF_PUTC(pad_c); } + // Pad byte is ' ', write '0x' after ' ' pad chars but before number. + if ((pad_c != '0') && need_0x) { NPF_PUTC('0'); NPF_PUTC(need_0x); } + } else +#endif + { if (need_0x) { NPF_PUTC('0'); NPF_PUTC(need_0x); } } // no pad, '0x' requested. + + // Write the converted payload + if (fs.conv_spec == NPF_FMT_SPEC_CONV_STRING) { + for (int i = 0; i < cbuf_len; ++i) { NPF_PUTC(cbuf[i]); } + } else { + if (sign_c) { NPF_PUTC(sign_c); } +#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1 + while (prec_pad-- > 0) { NPF_PUTC('0'); } // int precision leads. +#endif +#if NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS == 1 + if (fs.conv_spec == NPF_FMT_SPEC_CONV_BINARY) { + while (cbuf_len) { NPF_PUTC('0' + ((u.binval >> --cbuf_len) & 1)); } + } else +#endif + { while (cbuf_len-- > 0) { NPF_PUTC(cbuf[cbuf_len]); } } // payload is reversed + } + +#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1 + if (fs.left_justified && pad_c) { // Apply left-justified field width + while (field_pad-- > 0) { NPF_PUTC(pad_c); } + } +#endif + } + + ObjString *result = CopyString(textBuffer, pc_cnt.buffer.WriteIndex); + + free(textBuffer); + + return result; +} + +#undef NPF_PUTC + +#if NANOPRINTF_HAVE_GCC_WARNING_PRAGMAS + #pragma GCC diagnostic pop +#endif + +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +#endif // NANOPRINTF_IMPLEMENTATION_INCLUDED +#endif // NANOPRINTF_IMPLEMENTATION + +/* + nanoprintf is dual-licensed under both the "Unlicense" and the + "Zero-Clause BSD" (0BSD) licenses. The intent of this dual-licensing + structure is to make nanoprintf as consumable as possible in as many + environments / countries / companies as possible without any + encumberances. + + The text of the two licenses follows below: + + ============================== UNLICENSE ============================== + + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to + + ================================ 0BSD ================================= + + Copyright (C) 2019- by Charles Nicholson + + Permission to use, copy, modify, and/or distribute this software for + any purpose with or without fee is hereby granted. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/