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:
d
: Integersf
or %F
: Decimalss
: Stringsc
: Charactersx
or %X
: Hexadecimal integersb
or %b
: Binary integerso
: Octal integers%
: A literal percent sign character
Flags are optional, and must be one of the following:
0
: Pads the value with leading zeroes. See the width sub-specifier.-
: Left-justifies the result. See the width sub-specifier.#
: Prefixes something to the value depending on the conversion specifier:x
or X
: Prefixes the value with 0x
or 0X
respectively.b
or B
: Prefixes the value with 0b
or 0B
respectively.f
: Prefixes the value with a .
character.o
: Prefixes the value with a 0
character.
+
: Prefixes positive numbers with a plus sign.- A space character: If no sign character (
-
or +
) was written, a space character is written instead.
A
width sub-specifier is used in conjunction with the flags:
- A number: The amount of padding to add.
*
: This functions the same as the above, but the width is given in the next argument as an Integer value.
Precision specifiers are also supported:
.
followed by a number:- For Integer values, this pads the value with leading zeroes.
- For Decimal values, this specifies the number of digits to be printed after the decimal point (which is 6 by default).
- For String values, this is the maximum amount of characters to be printed.
.
followed by a *
: This functions the same as the above, but the precision is given in the next argument as an Integer value.
+ Parameters:
+
+ - string (String): The format string.
+ - values (any type): Variable arguments.
+
+ 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:
\
+\
+d
: Integers \
+f
or %F
: Decimals \
+s
: Strings \
+c
: Characters \
+x
or %X
: Hexadecimal integers \
+b
or %b
: Binary integers \
+o
: Octal integers \
+%
: A literal percent sign character \
+
\
+Flags are optional, and must be one of the following:
\
+\
+0
: Pads the value with leading zeroes. See the width sub-specifier. \
+-
: Left-justifies the result. See the width sub-specifier. \
+#
: Prefixes something to the value depending on the conversion specifier:\
+x
or X
: Prefixes the value with 0x
or 0X
respectively. \
+b
or B
: Prefixes the value with 0b
or 0B
respectively. \
+f
: Prefixes the value with a .
character. \
+o
: Prefixes the value with a 0
character. \
+
\
++
: Prefixes positive numbers with a plus sign. \
+- A space character: If no sign character (
-
or +
) was written, a space character is written instead. \
+
\
+A width sub-specifier is used in conjunction with the flags:
\
+\
+- A number: The amount of padding to add.
\
+*
: This functions the same as the above, but the width is given in the next argument as an Integer value. \
+
\
+Precision specifiers are also supported:
\
+\
+.
followed by a number:\
+- For Integer values, this pads the value with leading zeroes.
\
+- For Decimal values, this specifies the number of digits to be printed after the decimal point (which is 6 by default).
\
+- For String values, this is the maximum amount of characters to be printed.
\
+
\
+.
followed by a *
: This functions the same as the above, but the precision is given in the next argument as an Integer value. \
+
+ * \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.
+*/