From df1aaa031a777082a158bbf571cf37c1f95f3a60 Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 07:20:13 -0400 Subject: [PATCH 01/57] old msvc --- build.cmd | 111 ++++++++++++++++++++++++++++++++++++++++++++++ src/math_compat.h | 5 +++ 2 files changed, 116 insertions(+) create mode 100644 build.cmd diff --git a/build.cmd b/build.cmd new file mode 100644 index 000000000..e485a697f --- /dev/null +++ b/build.cmd @@ -0,0 +1,111 @@ +@echo off +setlocal enabledelayedexpansion + +if "%CC%"=="" set CC=cl +if "%CFLAGS%"=="" set CFLAGS=/O2 /DNDEBUG /nologo + +if not defined DESKTOP_BACKEND set DESKTOP_BACKEND=glfw3 +if not defined AUDIO_BACKEND set AUDIO_BACKEND=miniaudio + +if defined DISABLE_LEGACY_GL if defined DISABLE_MODERN_GL ( + echo must enable at least 1 renderer + exit /b 1 +) + +if defined DISABLE_WAD14 if defined DISABLE_WAD16 if defined DISABLE_WAD17 ( + echo must enable at least 1 bytecode version + exit /b 1 +) + +set DEFINES= +set DEFINES=%DEFINES% /DENABLE_VM_GML_PROFILER +set DEFINES=%DEFINES% /DENABLE_VM_OPCODE_PROFILER +set DEFINES=%DEFINES% /DENABLE_VM_STUB_LOGS +set DEFINES=%DEFINES% /DENABLE_VM_TRACING +set DEFINES=%DEFINES% /DBUTTERSCOTCH_COMMIT_DATE=\"unknown\" +set DEFINES=%DEFINES% /DBUTTERSCOTCH_COMMIT_HASH=\"unknown\" +set DEFINES=%DEFINES% /D_CRT_SECURE_NO_WARNINGS +set DEFINES=%DEFINES% /DWIN32_LEAN_AND_MEAN +set DEFINES=%DEFINES% /DNO_STRTOK_R + +if not defined DISABLE_WAD14 set DEFINES=%DEFINES% /DENABLE_WAD14 +if not defined DISABLE_WAD16 set DEFINES=%DEFINES% /DENABLE_WAD16 +if not defined DISABLE_WAD17 set DEFINES=%DEFINES% /DENABLE_WAD17 +if not defined DISABLE_LEGACY_GL set DEFINES=%DEFINES% /DENABLE_LEGACY_GL +if not defined DISABLE_MODERN_GL set DEFINES=%DEFINES% /DENABLE_MODERN_GL + +if "%DESKTOP_BACKEND%"=="glfw3" set DEFINES=%DEFINES% /DUSE_GLFW3 +if "%DESKTOP_BACKEND%"=="glfw2" set DEFINES=%DEFINES% /DUSE_GLFW2 +if "%DESKTOP_BACKEND%"=="sdl1" set DEFINES=%DEFINES% /DUSE_SDL1 +if "%DESKTOP_BACKEND%"=="sdl2" set DEFINES=%DEFINES% /DUSE_SDL2 +if "%DESKTOP_BACKEND%"=="sdl3" set DEFINES=%DEFINES% /DUSE_SDL3 +if "%AUDIO_BACKEND%"=="miniaudio" set DEFINES=%DEFINES% /DUSE_MINIAUDIO +if "%AUDIO_BACKEND%"=="openal" set DEFINES=%DEFINES% /DUSE_OPENAL + +set INCLUDES= +set INCLUDES=%INCLUDES% /I. +set INCLUDES=%INCLUDES% /Isrc +set INCLUDES=%INCLUDES% /Ivendor/stb/ds +set INCLUDES=%INCLUDES% /Isrc/image +set INCLUDES=%INCLUDES% /Ivendor/stb/image +set INCLUDES=%INCLUDES% /Ivendor/stb/vorbis +set INCLUDES=%INCLUDES% /Ivendor/md5 +set INCLUDES=%INCLUDES% /Ivendor/sha1 +set INCLUDES=%INCLUDES% /Ivendor/base64 +set INCLUDES=%INCLUDES% /Ivendor/bzip2 +set INCLUDES=%INCLUDES% /Isrc/desktop +set INCLUDES=%INCLUDES% /Icompat/getopt +set INCLUDES=%INCLUDES% /Ivendor/glad/include +if not defined DISABLE_LEGACY_GL set INCLUDES=%INCLUDES% /Isrc/gl_common /Isrc/gl_legacy /Isrc/gl +if not defined DISABLE_MODERN_GL ( + if defined DISABLE_LEGACY_GL set INCLUDES=%INCLUDES% /Isrc/gl_common + set INCLUDES=%INCLUDES% /Isrc/gl +) +if "%AUDIO_BACKEND%"=="miniaudio" set INCLUDES=%INCLUDES% /Isrc/audio/miniaudio /Ivendor/miniaudio +if "%AUDIO_BACKEND%"=="openal" set INCLUDES=%INCLUDES% /Isrc/audio/openal + +set LIBS=winmm.lib +if "%DESKTOP_BACKEND%"=="glfw3" ( + if defined GLFW3_LIBS ( set LIBS=%LIBS% %GLFW3_LIBS% + ) else set LIBS=%LIBS% glfw3.lib opengl32.lib gdi32.lib +) else if "%DESKTOP_BACKEND%"=="glfw2" ( + if defined GLFW2_LIBS ( set LIBS=%LIBS% %GLFW2_LIBS% + ) else set LIBS=%LIBS% glfw.lib opengl32.lib gdi32.lib +) else if "%DESKTOP_BACKEND%"=="sdl1" ( + if defined SDL1_LIBS ( set LIBS=%LIBS% %SDL1_LIBS% + ) else set LIBS=%LIBS% sdl.lib +) else if "%DESKTOP_BACKEND%"=="sdl2" ( + if defined SDL2_LIBS ( set LIBS=%LIBS% %SDL2_LIBS% + ) else set LIBS=%LIBS% sdl2.lib +) else if "%DESKTOP_BACKEND%"=="sdl3" ( + if defined SDL3_LIBS ( set LIBS=%LIBS% %SDL3_LIBS% + ) else set LIBS=%LIBS% sdl3.lib +) + +set SRCS= +set SRCS=%SRCS% src\*.c +set SRCS=%SRCS% src\image\*.c +set SRCS=%SRCS% src\desktop\*.c +set SRCS=%SRCS% "src\desktop\backends\%DESKTOP_BACKEND%.c" +set SRCS=%SRCS% vendor\bzip2\*.c +set SRCS=%SRCS% vendor\md5\*.c +set SRCS=%SRCS% vendor\sha1\*.c +set SRCS=%SRCS% vendor\base64\*.c +set SRCS=%SRCS% vendor\glad\src\glad.c +if not defined DISABLE_LEGACY_GL set SRCS=%SRCS% src\gl_common\*.c src\gl_legacy\*.c +if not defined DISABLE_MODERN_GL ( + if defined DISABLE_LEGACY_GL set SRCS=%SRCS% src\gl_common\*.c + set SRCS=%SRCS% src\gl\*.c +) +if "%AUDIO_BACKEND%"=="miniaudio" set SRCS=%SRCS% src\audio\miniaudio\*.c +if "%AUDIO_BACKEND%"=="openal" set SRCS=%SRCS% src\audio\openal\*.c + +if not exist build mkdir build + +for %%f in (%SRCS%) do ( + %CC% %CFLAGS% %DEFINES% %INCLUDES% /c "%%f" /Fobuild\ + if errorlevel 1 exit /b 1 +) + +%CC% %CFLAGS% build\*.obj /Febuild\butterscotch.exe /link %LIBS% %EXTRALIBS% +if errorlevel 1 exit /b 1 diff --git a/src/math_compat.h b/src/math_compat.h index fd7459f82..93f46735a 100644 --- a/src/math_compat.h +++ b/src/math_compat.h @@ -106,6 +106,11 @@ static float roundf(float x) { #endif +#if defined(_MSC_VER) && _MSC_VER < 1800 +#define isinf(x) (!_finite(x) && !_isnan(x)) +#define isnan(x) _isnan(x) +#endif + #ifndef INFINITY #define INFINITY (1.0f / 0.0f) #endif From dc4fc6270ba41462f0125989c8bf27f2f6406d9d Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 07:23:48 -0400 Subject: [PATCH 02/57] a --- src/math_compat.h | 5 ----- src/real_type.h | 5 +++++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/math_compat.h b/src/math_compat.h index 93f46735a..fd7459f82 100644 --- a/src/math_compat.h +++ b/src/math_compat.h @@ -106,11 +106,6 @@ static float roundf(float x) { #endif -#if defined(_MSC_VER) && _MSC_VER < 1800 -#define isinf(x) (!_finite(x) && !_isnan(x)) -#define isnan(x) _isnan(x) -#endif - #ifndef INFINITY #define INFINITY (1.0f / 0.0f) #endif diff --git a/src/real_type.h b/src/real_type.h index c65474555..d14c333e3 100644 --- a/src/real_type.h +++ b/src/real_type.h @@ -6,6 +6,11 @@ #include #include +#if defined(_MSC_VER) && _MSC_VER < 1800 +#define isinf(x) (!_finite(x) && !_isnan(x)) +#define isnan(x) _isnan(x) +#endif + #ifdef USE_FLOAT_REALS typedef float GMLReal; From 253cd6b231ad64becfc6776c6862cc1c56d80c8a Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 07:34:28 -0400 Subject: [PATCH 03/57] fix --- src/real_type.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/real_type.h b/src/real_type.h index d14c333e3..9b9e1ba37 100644 --- a/src/real_type.h +++ b/src/real_type.h @@ -7,6 +7,7 @@ #include #if defined(_MSC_VER) && _MSC_VER < 1800 +#include #define isinf(x) (!_finite(x) && !_isnan(x)) #define isnan(x) _isnan(x) #endif From 72fba1b7ca4ddbdf3ec01f5e25337b541dbb589b Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 08:30:51 -0400 Subject: [PATCH 04/57] im old --- build.cmd | 15 + compat/configure.cmd | 33 + compat/stdio/nanoprintf.h | 1695 ++++++++++++++++++++++++++++++++ compat/stdio/nanoprintf_impl.c | 2 + compat/stdio/stdio.h | 9 + 5 files changed, 1754 insertions(+) create mode 100644 compat/configure.cmd create mode 100644 compat/stdio/nanoprintf.h create mode 100644 compat/stdio/nanoprintf_impl.c create mode 100644 compat/stdio/stdio.h diff --git a/build.cmd b/build.cmd index e485a697f..73a48351b 100644 --- a/build.cmd +++ b/build.cmd @@ -4,6 +4,19 @@ setlocal enabledelayedexpansion if "%CC%"=="" set CC=cl if "%CFLAGS%"=="" set CFLAGS=/O2 /DNDEBUG /nologo +if not exist compat\tmp mkdir compat\tmp +>compat\tmp\cc-new echo.%CC% +if not exist compat\config.mk goto run_config +if not exist compat\tmp\cc goto run_config +fc compat\tmp\cc compat\tmp\cc-new >nul 2>&1 +if errorlevel 1 goto run_config +goto skip_config +:run_config +call compat\configure.cmd +copy /y compat\tmp\cc-new compat\tmp\cc >nul +:skip_config +del compat\tmp\cc-new 2>nul + if not defined DESKTOP_BACKEND set DESKTOP_BACKEND=glfw3 if not defined AUDIO_BACKEND set AUDIO_BACKEND=miniaudio @@ -100,6 +113,8 @@ if not defined DISABLE_MODERN_GL ( if "%AUDIO_BACKEND%"=="miniaudio" set SRCS=%SRCS% src\audio\miniaudio\*.c if "%AUDIO_BACKEND%"=="openal" set SRCS=%SRCS% src\audio\openal\*.c +if exist compat\config.bat call compat\config.bat + if not exist build mkdir build for %%f in (%SRCS%) do ( diff --git a/compat/configure.cmd b/compat/configure.cmd new file mode 100644 index 000000000..bdb87f6dc --- /dev/null +++ b/compat/configure.cmd @@ -0,0 +1,33 @@ +@echo off +setlocal enabledelayedexpansion + +if "%CC%"=="" ( + echo Don't run this directly + exit /b 1 +) + +cd /d "%~dp0" + +if not exist tmp mkdir tmp + +type nul > tmp\config.log + +>tmp\test.c echo.#include ^ +>>tmp\test.c echo.int main(void^){ +>>tmp\test.c echo. char buf[64]; +>>tmp\test.c echo. return snprintf(buf, sizeof(buf^), "test"^); +>>tmp\test.c echo.} + +echo.checking for snprintf: >> tmp\config.log +%CC% /nologo /Oi- tmp\test.c /c /Fo:tmp\test.obj >> tmp\config.log 2>&1 +if %errorlevel% neq 0 ( + echo checking for snprintf: no + > config.bat echo @echo off + >> config.bat echo set DEFINES=%%DEFINES%% /DNO_SNPRINTF + >> config.bat echo set INCLUDES=%%INCLUDES%% /Icompat\stdio + >> config.bat echo set SRCS=%%SRCS%% compat\stdio\nanoprintf_impl.c +) else ( + echo checking for snprintf: yes +) + +del tmp\test.c tmp\test.obj 2>nul diff --git a/compat/stdio/nanoprintf.h b/compat/stdio/nanoprintf.h new file mode 100644 index 000000000..07e8d32e0 --- /dev/null +++ b/compat/stdio/nanoprintf.h @@ -0,0 +1,1695 @@ +/* nanoprintf v0.6.0: 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. */ + +#ifndef NPF_H_INCLUDED +#define NPF_H_INCLUDED + +#ifdef NANOPRINTF_CONFIG_FILE +#include NANOPRINTF_CONFIG_FILE +#endif + +#include +#include + +typedef void (*npf_putc)(int c, void *ctx); + +// 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 + +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #define NPF_PRINTF_ATTR(FORMAT_INDEX, VARGS_INDEX) \ + __attribute__((format(printf, FORMAT_INDEX, VARGS_INDEX))) +#else + #define NPF_PRINTF_ATTR(FORMAT_INDEX, VARGS_INDEX) +#endif + +#if defined(NANOPRINTF_USE_FLOAT_SINGLE_PRECISION) && \ + (NANOPRINTF_USE_FLOAT_SINGLE_PRECISION == 1) +#define NPF_PRINTF_SP_ATTR NPF_PRINTF_ATTR(3, 0) +#define NPF_MAP_ARGS(...) NPF__MAP(NPF__WRAP, __VA_ARGS__) +typedef struct { float val; } npf_float_t; +#define npf_snprintf_ npf_snprintf_sp_ +#define npf_pprintf_ npf_pprintf_sp_ +#define npf_vsnprintf npf_vsnprintf_sp +#define npf_vpprintf npf_vpprintf_sp +#else +#define NPF_PRINTF_SP_ATTR NPF_PRINTF_ATTR(3, 4) +#define NPF_MAP_ARGS(...) __VA_ARGS__ +#endif + +#ifdef __cplusplus +#define NPF_RESTRICT +extern "C" { +#else +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) +#define NPF_RESTRICT restrict +#else +#define NPF_RESTRICT +#endif +#endif + +NPF_VISIBILITY int npf_snprintf_(char * NPF_RESTRICT buffer, + size_t bufsz, + const char * NPF_RESTRICT format, ...) + NPF_PRINTF_SP_ATTR; + +NPF_VISIBILITY int npf_pprintf_(npf_putc pc, + void * NPF_RESTRICT pc_ctx, + char const * NPF_RESTRICT format, ...) + NPF_PRINTF_SP_ATTR; + +// Public API + +// The npf_ functions all return the number of bytes required to express the +// fully-formatted string, not including the null terminator character. +// The npf_ functions do not return negative values, since the lack of 'l' length +// modifier support makes encoding errors impossible. + +#define npf_snprintf(buf, sz, ...) npf_snprintf_((buf), (sz), NPF_MAP_ARGS(__VA_ARGS__)) +#define npf_pprintf(pc, ctx, ...) npf_pprintf_((pc), (ctx), NPF_MAP_ARGS(__VA_ARGS__)) + +NPF_VISIBILITY int npf_vsnprintf(char * NPF_RESTRICT buffer, + size_t bufsz, + char const * NPF_RESTRICT format, + va_list vlist) NPF_PRINTF_ATTR(3, 0); + +NPF_VISIBILITY int npf_vpprintf(npf_putc pc, + void * NPF_RESTRICT pc_ctx, + char const * NPF_RESTRICT format, + va_list vlist) NPF_PRINTF_ATTR(3, 0); + +#ifdef __cplusplus +} +#endif + +#endif // NPF_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 NPF_IMPLEMENTATION_INCLUDED +#define NPF_IMPLEMENTATION_INCLUDED + +#include +#include + +// 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_LARGE_FORMAT_SPECIFIERS) && \ + !defined(NANOPRINTF_USE_SMALL_FORMAT_SPECIFIERS) && \ + !defined(NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS) && \ + !defined(NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS) && \ + !defined(NANOPRINTF_USE_ALT_FORM_FLAG) && \ + !defined(NANOPRINTF_USE_FLOAT_SINGLE_PRECISION) + #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_LARGE_FORMAT_SPECIFIERS 0 + #define NANOPRINTF_USE_SMALL_FORMAT_SPECIFIERS 1 + #define NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS 0 + #define NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS 0 + #define NANOPRINTF_USE_ALT_FORM_FLAG 1 + #define NANOPRINTF_USE_FLOAT_SINGLE_PRECISION 0 +#endif + +// Single-precision mode defaults to off unless explicitly enabled. +#ifndef NANOPRINTF_USE_FLOAT_SINGLE_PRECISION + #define NANOPRINTF_USE_FLOAT_SINGLE_PRECISION 0 +#endif + +// Optional flag, defaults to 0 if not explicitly configured. +#ifndef NANOPRINTF_USE_FLOAT_HEX_FORMAT_SPECIFIER + #define NANOPRINTF_USE_FLOAT_HEX_FORMAT_SPECIFIER 0 +#endif + +// Optional flag, defaults to 0 if not explicitly configured. Extracts digits +// without integer division; smaller on cores without a hardware divider. +#ifndef NANOPRINTF_USE_DIVISION_FREE_CONVERSION + #define NANOPRINTF_USE_DIVISION_FREE_CONVERSION 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_LARGE_FORMAT_SPECIFIERS + #error NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS must be #defined to 0 or 1 +#endif +#ifndef NANOPRINTF_USE_SMALL_FORMAT_SPECIFIERS + #error NANOPRINTF_USE_SMALL_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 +#ifndef NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS + #error NANOPRINTF_USE_WRITEBACK_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 + +#if (NANOPRINTF_USE_FLOAT_HEX_FORMAT_SPECIFIER == 1) && \ + (NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 0) + #error Float format specifiers must be enabled if float hex support is enabled. +#endif + +#if (NANOPRINTF_USE_FLOAT_SINGLE_PRECISION == 1) && \ + (NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 0) + #error Single precision requires float format specifiers to be enabled. +#endif + +#if NANOPRINTF_USE_FLOAT_SINGLE_PRECISION == 1 + #if defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL + #error Single-precision float mode requires /Zc:preprocessor on MSVC. + #endif + #ifdef __cplusplus + #if __cplusplus < 201103L && !defined(_MSC_VER) + #error Single-precision float wrapping requires C++11 or later. + #endif + #else + #if !(defined(__GNUC__) || defined(__clang__)) + #if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 201112L) + #error Single-precision float wrapping requires C11 or later (or GCC/Clang). + #endif + #endif + #endif +#endif + +// The conversion buffer must fit at least UINT64_MAX in octal format with the leading '0'. +// When floats are enabled, a larger buffer is needed for values like FLT_MAX / DBL_MAX. +#ifndef NANOPRINTF_CONVERSION_BUFFER_SIZE + #if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1 + #define NANOPRINTF_CONVERSION_BUFFER_SIZE 64 + #else + #define NANOPRINTF_CONVERSION_BUFFER_SIZE 23 + #endif +#endif +#if NANOPRINTF_CONVERSION_BUFFER_SIZE < 23 + #error The size of the conversion buffer must be at least 23 bytes. +#endif + +// intmax_t / uintmax_t require stdint from c99 / c++11 +#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1 + #ifndef _MSC_VER + #ifdef __cplusplus + #if __cplusplus < 201103L + #error large format specifier support requires C++11 or later. + #endif + #else + #if __STDC_VERSION__ < 199409L + #error nanoprintf requires C99 or later. + #endif + #endif + #endif +#endif + +// Figure out if we can disable warnings with pragmas. +#ifdef __clang__ + #define NPF_CLANG 1 + #define NPF_GCC_PAST_4_6 0 +#else + #define NPF_CLANG 0 + #if defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 6))) + #define NPF_GCC_PAST_4_6 1 + #else + #define NPF_GCC_PAST_4_6 0 + #endif +#endif + +#if NPF_CLANG || NPF_GCC_PAST_4_6 + #define NPF_HAVE_GCC_WARNING_PRAGMAS 1 +#else + #define NPF_HAVE_GCC_WARNING_PRAGMAS 0 +#endif + +#if NPF_HAVE_GCC_WARNING_PRAGMAS + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wpragmas" + #pragma GCC diagnostic ignored "-Wfloat-equal" + #pragma GCC diagnostic ignored "-Wgnu-statement-expression-from-macro-expansion" + #pragma GCC diagnostic ignored "-Wimplicit-fallthrough" + #pragma GCC diagnostic ignored "-Wpadded" + #pragma GCC diagnostic ignored "-Wpedantic" + #pragma GCC diagnostic ignored "-Wunused-function" + + #ifdef __cplusplus + #pragma GCC diagnostic ignored "-Wold-style-cast" + #endif + + #if NPF_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 NPF_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)) + #define NPF_FORCE_INLINE inline __attribute__((always_inline)) + + #define NPF_MIN(X, Y) ({ \ + __typeof__(X) const x = (X); __typeof__(Y) const y = (Y); x <= y ? x : y; }) + #define NPF_MAX(X, Y) ({ \ + __typeof__(X) const x = (X); __typeof__(Y) const y = (Y); x >= y ? x : y; }) +#else + #if defined(_MSC_VER) + #define NPF_NOINLINE __declspec(noinline) + #define NPF_FORCE_INLINE inline __forceinline + #else + #define NPF_NOINLINE + #define NPF_FORCE_INLINE + #endif + + #define NPF_MIN(X, Y) ((X) <= (Y) ? (X) : (Y)) + #define NPF_MAX(X, Y) ((X) >= (Y) ? (X) : (Y)) +#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_LEN_MOD_NONE, +#if NANOPRINTF_USE_SMALL_FORMAT_SPECIFIERS == 1 + NPF_FMT_SPEC_LEN_MOD_SHORT, // 'h' + NPF_FMT_SPEC_LEN_MOD_CHAR, // 'hh' +#endif + NPF_FMT_SPEC_LEN_MOD_LONG, // 'l' + NPF_FMT_SPEC_LEN_MOD_LONG_DOUBLE, // 'L' +#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1 + NPF_FMT_SPEC_LEN_MOD_LARGE_LONG_LONG, // 'll' + NPF_FMT_SPEC_LEN_MOD_LARGE_INTMAX, // 'j' + NPF_FMT_SPEC_LEN_MOD_LARGE_SIZET, // 'z' + NPF_FMT_SPEC_LEN_MOD_LARGE_PTRDIFFT, // 't' +#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' + NPF_FMT_SPEC_CONV_UNSIGNED_INT, // 'u' + NPF_FMT_SPEC_CONV_POINTER, // 'p' +#if NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS == 1 + NPF_FMT_SPEC_CONV_WRITEBACK, // 'n' +#endif +#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1 + NPF_FMT_SPEC_CONV_FLOAT_DEC, // 'f', 'F' + NPF_FMT_SPEC_CONV_FLOAT_SCI, // 'e', 'E' + NPF_FMT_SPEC_CONV_FLOAT_SHORTEST, // 'g', 'G' +#if NANOPRINTF_USE_FLOAT_HEX_FORMAT_SPECIFIER == 1 + NPF_FMT_SPEC_CONV_FLOAT_HEX, // 'a', 'A' +#endif +#endif +}; + +// Assert range order/comparisons to enforce C standard ordering +#define NPF_CONV_ORDER_ASSERT(NAME, COND) \ + typedef char npf_assert_##NAME[(COND) ? 1 : -1] +NPF_CONV_ORDER_ASSERT(text_convs_before_numeric, + (NPF_FMT_SPEC_CONV_PERCENT < NPF_FMT_SPEC_CONV_SIGNED_INT) && + (NPF_FMT_SPEC_CONV_CHAR < NPF_FMT_SPEC_CONV_SIGNED_INT) && + (NPF_FMT_SPEC_CONV_STRING < NPF_FMT_SPEC_CONV_SIGNED_INT)); +#if NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS == 1 +NPF_CONV_ORDER_ASSERT(int_convs_contiguous, + NPF_FMT_SPEC_CONV_UNSIGNED_INT == NPF_FMT_SPEC_CONV_SIGNED_INT + 4); +#else +NPF_CONV_ORDER_ASSERT(int_convs_contiguous, + NPF_FMT_SPEC_CONV_UNSIGNED_INT == NPF_FMT_SPEC_CONV_SIGNED_INT + 3); +#endif +NPF_CONV_ORDER_ASSERT(pointer_after_int_convs, + NPF_FMT_SPEC_CONV_POINTER > NPF_FMT_SPEC_CONV_UNSIGNED_INT); +#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1 +#if NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS == 1 +NPF_CONV_ORDER_ASSERT(float_convs_last, + NPF_FMT_SPEC_CONV_FLOAT_DEC > NPF_FMT_SPEC_CONV_WRITEBACK); +#else +NPF_CONV_ORDER_ASSERT(float_convs_last, + NPF_FMT_SPEC_CONV_FLOAT_DEC > NPF_FMT_SPEC_CONV_POINTER); +#endif +#endif +#undef NPF_CONV_ORDER_ASSERT + +typedef struct npf_format_spec { +#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1 + int field_width; +#endif +#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1 + int prec; + uint8_t prec_opt; +#endif +#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1 + uint8_t field_width_opt; + char left_justified; // '-' + char leading_zero_pad; // '0' +#endif + char prepend; // ' ' or '+' +#if NANOPRINTF_USE_ALT_FORM_FLAG == 1 + char alt_form; // '#' +#endif + char case_adjust; // 'a' - 'A' , or 0 (must be non-negative to work) + uint8_t length_modifier; + uint8_t conv_spec; +} npf_format_spec_t; + +#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1 + typedef intmax_t npf_int_t; + typedef uintmax_t npf_uint_t; + #define NPF_UINT_IS_WIDE 1 // uintmax_t is at least 64 bits +#elif ULONG_MAX > UINTPTR_MAX + typedef long npf_int_t; + typedef unsigned long npf_uint_t; + #define NPF_UINT_IS_WIDE (ULONG_MAX > 0xFFFFFFFFu) +#else + typedef intptr_t npf_int_t; + typedef uintptr_t npf_uint_t; + #define NPF_UINT_IS_WIDE (UINTPTR_MAX > 0xFFFFFFFFu) +#endif + +typedef struct npf_bufputc_ctx { + char *dst; // moving cursor; advances on each write while len > 0. + size_t len; // remaining capacity; decrements on each successful write. +} npf_bufputc_ctx_t; + +#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1 + typedef char npf_size_is_ptrdiff[(sizeof(size_t) == sizeof(ptrdiff_t)) ? 1 : -1]; + typedef ptrdiff_t npf_ssize_t; + typedef size_t npf_uptrdiff_t; +#endif + +#ifdef _MSC_VER + #include +#endif + +// Returns a pointer one past the last consumed character on success, null on +// failure. A pointer return inlines into npf_vpprintf with smaller code than +// a length return (the caller continues from the returned cursor directly). +static char const *npf_parse_format_spec_end(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->prepend = 0; +#if NANOPRINTF_USE_ALT_FORM_FLAG == 1 + out_spec->alt_form = 0; +#endif + + for (;;) { // cur points at the leading '%' character + switch (*++cur) { // Optional flags; '\0' exits via default. +#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1 + case '-': out_spec->left_justified = '-'; continue; + case '0': out_spec->leading_zero_pad = 1; continue; +#endif + case '+': + case ' ': if (out_spec->prepend != '+') { out_spec->prepend = *cur; } continue; +#if NANOPRINTF_USE_ALT_FORM_FLAG == 1 + case '#': out_spec->alt_form = '#'; continue; +#endif + default: break; + } + break; + } + +#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1 + out_spec->field_width = 0; + out_spec->field_width_opt = NPF_FMT_SPEC_OPT_NONE; + if (*cur == '*') { + out_spec->field_width_opt = NPF_FMT_SPEC_OPT_STAR; + ++cur; + } else { + while ((unsigned)(*cur - '0') < 10u) { + 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 ((unsigned)(*cur - '0') < 10u) { + out_spec->prec = (out_spec->prec * 10) + (*cur++ - '0'); + } + } + } +#endif + + out_spec->length_modifier = NPF_FMT_SPEC_LEN_MOD_NONE; + switch (*cur++) { // Length modifier +#if NANOPRINTF_USE_SMALL_FORMAT_SPECIFIERS == 1 + case 'h': + out_spec->length_modifier = NPF_FMT_SPEC_LEN_MOD_SHORT; + if (*cur == 'h') { + out_spec->length_modifier = NPF_FMT_SPEC_LEN_MOD_CHAR; + ++cur; + } + break; +#endif + case 'l': + out_spec->length_modifier = NPF_FMT_SPEC_LEN_MOD_LONG; +#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1 + if (*cur == 'l') { + out_spec->length_modifier = NPF_FMT_SPEC_LEN_MOD_LARGE_LONG_LONG; + ++cur; + } +#endif + break; +#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1 + case 'L': out_spec->length_modifier = NPF_FMT_SPEC_LEN_MOD_LONG_DOUBLE; break; +#endif +#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1 + case 'j': out_spec->length_modifier = NPF_FMT_SPEC_LEN_MOD_LARGE_INTMAX; break; + case 'z': out_spec->length_modifier = NPF_FMT_SPEC_LEN_MOD_LARGE_SIZET; break; + case 't': out_spec->length_modifier = NPF_FMT_SPEC_LEN_MOD_LARGE_PTRDIFFT; break; +#endif + default: --cur; break; + } + + // Conversion specifier. Look up the conv_spec from a 24-byte table indexed by + // (lowercased letter - 'a'). '%' is handled out-of-line since it's the only + // non-letter conversion. case_adjust gets bit 5 of the original char. + char const c = *cur++; + uint_fast8_t cs; + if (c == '%') { + cs = NPF_FMT_SPEC_CONV_PERCENT; + } else { + static const uint8_t lookup[24] = { +#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1 && NANOPRINTF_USE_FLOAT_HEX_FORMAT_SPECIFIER == 1 + NPF_FMT_SPEC_CONV_FLOAT_HEX, // 'a' +#else + 0, // 'a' +#endif +#if NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS == 1 + NPF_FMT_SPEC_CONV_BINARY, // 'b' +#else + 0, // 'b' +#endif + NPF_FMT_SPEC_CONV_CHAR, // 'c' + NPF_FMT_SPEC_CONV_SIGNED_INT, // 'd' +#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1 + NPF_FMT_SPEC_CONV_FLOAT_SCI, // 'e' + NPF_FMT_SPEC_CONV_FLOAT_DEC, // 'f' + NPF_FMT_SPEC_CONV_FLOAT_SHORTEST, // 'g' +#else + 0, 0, 0, // 'e', 'f', 'g' +#endif + 0, // 'h' (length modifier) + NPF_FMT_SPEC_CONV_SIGNED_INT, // 'i' + 0, 0, 0, 0, // 'j', 'k', 'l', 'm' +#if NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS == 1 + NPF_FMT_SPEC_CONV_WRITEBACK, // 'n' +#else + 0, // 'n' +#endif + NPF_FMT_SPEC_CONV_OCTAL, // 'o' + NPF_FMT_SPEC_CONV_POINTER, // 'p' + 0, 0, // 'q', 'r' + NPF_FMT_SPEC_CONV_STRING, // 's' + 0, // 't' + NPF_FMT_SPEC_CONV_UNSIGNED_INT, // 'u' + 0, 0, // 'v', 'w' + NPF_FMT_SPEC_CONV_HEX_INT, // 'x' + }; + unsigned const idx = (unsigned)((c | 32) - 'a'); + if (idx >= sizeof(lookup) || !(cs = lookup[idx])) { return NULL; } + } + out_spec->conv_spec = (uint8_t)cs; + out_spec->case_adjust = (char)(c & 32); // 32 for lowercase, 0 for uppercase + + return cur; +} + +// Length-returning facade over npf_parse_format_spec_end; 0 means failure. +static NPF_FORCE_INLINE int npf_parse_format_spec(char const *format, + npf_format_spec_t *out_spec) { + char const *const end = npf_parse_format_spec_end(format, out_spec); + return end ? (int)(end - format) : 0; +} + +#if NANOPRINTF_USE_DIVISION_FREE_CONVERSION == 1 +// Calculate div by 10 justing a shift and mask to avoid using hw div operands +static NPF_NOINLINE uint32_t npf_div10(uint32_t n) { + uint32_t q = (n >> 1) + (n >> 2); + q += q >> 4; + q += q >> 8; + q += q >> 16; + q >>= 3; + return q + ((n - (q * 10u) + 6u) >> 4); +} +#endif + +static NPF_NOINLINE char *npf_utoa_rev_end( + npf_uint_t val, char *buf, uint_fast8_t base, char case_adj) { +#if (NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1) || \ + ((NANOPRINTF_USE_DIVISION_FREE_CONVERSION == 1) && NPF_UINT_IS_WIDE) + // Use shift and subtract here to avoid hw div operation + while (val > 0xFFFFFFFFu) { + npf_uint_t q = 0, r = 0; + for (int i = (int)(sizeof(val) * 8) - 1; i >= 0; --i) { + r = (r << 1) | ((val >> i) & 1); + if (r >= (npf_uint_t)base) { r -= base; q |= (npf_uint_t)1 << i; } + } + int_fast8_t const d = (int_fast8_t)r; + *buf++ = (char)(((d < 10) ? '0' : ('A' - 10 + case_adj)) + d); + val = q; + } + uint32_t v32 = (uint32_t)val; +#else + npf_uint_t v32 = val; +#endif + do { +#if NANOPRINTF_USE_DIVISION_FREE_CONVERSION == 1 + int_fast8_t d; + if (base == 10u) { + uint32_t const q = npf_div10((uint32_t)v32); + d = (int_fast8_t)((uint32_t)v32 - (q * 10u)); + v32 = q; + } else { // base 8 or 16: shift and mask + d = (int_fast8_t)(v32 & (base - 1u)); + v32 >>= (base + 16u) >> 3; // 8 -> 3, 16 -> 4 + } +#else + int_fast8_t const d = (int_fast8_t)(v32 % base); + v32 /= base; +#endif + *buf++ = (char)(((d < 10) ? '0' : ('A' - 10 + case_adj)) + d); + } while (v32); + return buf; +} + +// Length-returning facade over npf_utoa_rev_end. +static NPF_FORCE_INLINE int npf_utoa_rev( + npf_uint_t val, char *buf, uint_fast8_t base, char case_adj) { + return (int)(npf_utoa_rev_end(val, buf, base, case_adj) - buf); +} + +#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1 + +#include + +#if NANOPRINTF_USE_FLOAT_SINGLE_PRECISION == 1 + typedef float npf_real_t; + #define NPF_REAL_MANT_DIG FLT_MANT_DIG + #define NPF_REAL_MAX_EXP FLT_MAX_EXP +#else + typedef double npf_real_t; + #define NPF_REAL_MANT_DIG DBL_MANT_DIG + #define NPF_REAL_MAX_EXP DBL_MAX_EXP +#endif + +#if (NPF_REAL_MANT_DIG <= 11) && (NPF_REAL_MAX_EXP <= 16) + typedef uint_fast16_t npf_real_bin_t; + typedef int_fast8_t npf_ftoa_exp_t; +#elif (NPF_REAL_MANT_DIG <= 24) && (NPF_REAL_MAX_EXP <= 128) + typedef uint_fast32_t npf_real_bin_t; + typedef int_fast16_t npf_ftoa_exp_t; +#elif (NPF_REAL_MANT_DIG <= 53) && (NPF_REAL_MAX_EXP <= 1024) + typedef uint_fast64_t npf_real_bin_t; + typedef int_fast16_t npf_ftoa_exp_t; +#else + #error Unsupported width of the real 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_REAL_EXP_MASK = NPF_REAL_MAX_EXP * 2 - 1, + NPF_REAL_EXP_BIAS = NPF_REAL_MAX_EXP - 1, + NPF_REAL_MAN_BITS = NPF_REAL_MANT_DIG - 1, + NPF_REAL_BIN_BITS = sizeof(npf_real_bin_t) * CHAR_BIT, + NPF_REAL_SIGN_POS = sizeof(npf_real_t) * CHAR_BIT - 1, + NPF_FTOA_MAN_BITS = sizeof(npf_ftoa_man_t) * CHAR_BIT, + NPF_FTOA_SHIFT_BITS = + ((NPF_FTOA_MAN_BITS < NPF_REAL_MANT_DIG) ? NPF_FTOA_MAN_BITS : NPF_REAL_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 NPF_FORCE_INLINE npf_real_bin_t npf_real_to_int_rep(npf_real_t f) { + // Union-cast is UB pre-C11 and in all C++; the compiler optimizes the code below. + npf_real_bin_t bin = 0; + char const *src = (char const *)&f; + char *dst = (char *)&bin; + for (uint_fast8_t i = 0; i < sizeof(f); ++i) { dst[i] = src[i]; } + return bin; +} + +#if NANOPRINTF_USE_DIVISION_FREE_CONVERSION == 1 +// Variable shifts of 64-bit values call sw helpers on archs +// without 64-bit shifters. Perfer 1 bit shits to keep in 32 bit word spce. +static NPF_FORCE_INLINE npf_real_bin_t npf_bin_shr(npf_real_bin_t v, int_fast8_t s) { + if (sizeof(v) > sizeof(uint32_t)) { while (s--) { v >>= 1; } return v; } + return (npf_real_bin_t)(v >> s); +} +static NPF_FORCE_INLINE npf_real_bin_t npf_bin_shl(npf_real_bin_t v, int_fast8_t s) { + if (sizeof(v) > sizeof(uint32_t)) { while (s--) { v <<= 1; } return v; } + return (npf_real_bin_t)(v << s); +} + #define NPF_BIN_SHR(V, S) npf_bin_shr((V), (int_fast8_t)(S)) + #define NPF_BIN_SHL(V, S) npf_bin_shl((V), (int_fast8_t)(S)) +#else + #define NPF_BIN_SHR(V, S) ((npf_real_bin_t)((V) >> (S))) + #define NPF_BIN_SHL(V, S) ((npf_real_bin_t)((V) << (S))) +#endif + +static int npf_ftoa_rev(char *buf, npf_format_spec_t const *spec, npf_real_t f) { + // Packed reversed specials "NAN"/"FNI"(inf)/"RRE"(err), selected by offset. + static char const specials[] = "NAN\0FNI\0RRE"; + char const *ret = NULL; + npf_real_bin_t bin = npf_real_to_int_rep(f); + + // 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_REAL_MAN_BITS) & NPF_REAL_EXP_MASK); + + bin &= ((npf_real_bin_t)0x1 << NPF_REAL_MAN_BITS) - 1; + if (!((unsigned)(exp + 1) & NPF_REAL_EXP_MASK)) { // special value + ret = specials + (bin ? 0 : 4); + goto exit; + } + if (spec->prec > (NANOPRINTF_CONVERSION_BUFFER_SIZE - 2)) { goto exit; } + if (exp) { // normal number + bin |= (npf_real_bin_t)0x1 << NPF_REAL_MAN_BITS; + } else { // subnormal number + ++exp; + } + exp = (npf_ftoa_exp_t)(exp - NPF_REAL_EXP_BIAS); + + uint_fast8_t carry; carry = 0; + npf_ftoa_dec_t end, dec; dec = (npf_ftoa_dec_t)spec->prec; + if (dec +#if NANOPRINTF_USE_ALT_FORM_FLAG == 1 + || spec->alt_form +#endif + ) { + 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_REAL_MAN_BITS - shift_i); + if (shift_i) { + npf_real_bin_t const bin_i = NPF_BIN_SHR(bin, shift_i - 1); + carry = (uint_fast8_t)(bin_i & 0x1); + man_i = (npf_ftoa_man_t)(bin_i >> 1); + } else { + man_i = (npf_ftoa_man_t)bin; + } + + if (exp_i) { + exp = NPF_REAL_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_BITS - 1))) { + man_i = (npf_ftoa_man_t)((man_i << 1) | carry); carry = 0; + } else { + if (dec >= NANOPRINTF_CONVERSION_BUFFER_SIZE) { goto exit; } + buf[dec++] = '0'; +#if NANOPRINTF_USE_DIVISION_FREE_CONVERSION == 1 + if (sizeof(man_i) <= sizeof(uint32_t)) { // n/5 = 2*(n/10) + (n%10 >= 5) + uint32_t const q = npf_div10((uint32_t)man_i); + uint_fast8_t r = (uint_fast8_t)((uint32_t)man_i - (q * 10u)); + man_i = (npf_ftoa_man_t)(q * 2u); + if (r >= 5u) { r = (uint_fast8_t)(r - 5u); ++man_i; } + carry = (uint_fast8_t)((r + carry + 1u) >> 2); + } else +#endif + { + carry = (uint_fast8_t)(((uint_fast8_t)(man_i % 5) + carry + 1u) >> 2); + man_i /= 5; + } + } + } + } else { + man_i = 0; + } + end = dec; + + // Print the integer. A 32-bit man_i has at most 10 decimal digits, so one + // up-front capacity check replaces the per-digit check and npf_utoa_rev + // becomes the shared decimal emitter. (Conservative: a value needing fewer + // digits than the remaining capacity in the last 9 bytes now reports ERR.) + if ((sizeof(npf_ftoa_man_t) <= sizeof(uint32_t)) && + (sizeof(npf_uint_t) >= sizeof(uint32_t))) { + if (end > NANOPRINTF_CONVERSION_BUFFER_SIZE - 10) { goto exit; } + end = (npf_ftoa_dec_t)(npf_utoa_rev_end((npf_uint_t)man_i, buf + end, 10, 0) - buf); + } else { // man_i may be wider than npf_uint_t: emit in place + do { + 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_REAL_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_real_bin_t bin_f = + NPF_BIN_SHL(bin, (NPF_REAL_BIN_BITS - NPF_REAL_MAN_BITS) + shift_f); + + // This if-else statement can be completely optimized at compile time. + if (NPF_REAL_BIN_BITS > NPF_FTOA_MAN_BITS) { + man_f = (npf_ftoa_man_t)(bin_f >> ((unsigned)(NPF_REAL_BIN_BITS - + NPF_FTOA_MAN_BITS) % + NPF_REAL_BIN_BITS)); + carry = (uint_fast8_t)((bin_f >> ((unsigned)(NPF_REAL_BIN_BITS - + NPF_FTOA_MAN_BITS - 1) % + NPF_REAL_BIN_BITS)) & 0x1); + } else { + man_f = (npf_ftoa_man_t)((npf_ftoa_man_t)bin_f + << ((unsigned)(NPF_FTOA_MAN_BITS - + NPF_REAL_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_REAL_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'; } + char const c = buf[dec]; + if (c == '.') { continue; } + if (c == '9') { buf[dec] = '0'; } + else { buf[dec] = (char)(c + 1); carry = 0; } + } + + return (int)end; +exit: + if (!ret) { ret = specials + 8; } + uint_fast8_t i = 0; + do { buf[i] = (char)(ret[i] + spec->case_adjust); } while (ret[++i]); + return -(int)i; +} + +#if NANOPRINTF_USE_FLOAT_HEX_FORMAT_SPECIFIER == 1 + +// Hex float always operates on IEEE 754 binary64 (double). +// When not in single-precision mode, npf_real_* already handles double. +#if NANOPRINTF_USE_FLOAT_SINGLE_PRECISION == 1 +typedef uint_fast64_t npf_double_bin_t; +enum { + NPF_DOUBLE_EXP_MASK = 2047, + NPF_DOUBLE_EXP_BIAS = 1023, + NPF_DOUBLE_MAN_BITS = 52, +}; +static NPF_FORCE_INLINE npf_double_bin_t npf_double_to_int_rep(double f) { + npf_double_bin_t bin = 0; + char const *src = (char const *)&f; + char *dst = (char *)&bin; + for (uint_fast8_t i = 0; i < sizeof(f); ++i) { dst[i] = src[i]; } + return bin; +} +#else +typedef npf_real_bin_t npf_double_bin_t; +#define NPF_DOUBLE_EXP_MASK NPF_REAL_EXP_MASK +#define NPF_DOUBLE_EXP_BIAS NPF_REAL_EXP_BIAS +#define NPF_DOUBLE_MAN_BITS NPF_REAL_MAN_BITS +#define npf_double_to_int_rep(f) npf_real_to_int_rep(f) +#endif + +static NPF_NOINLINE int npf_atoa_rev( + char *buf, npf_format_spec_t const *spec, double f) { + npf_double_bin_t bin = npf_double_to_int_rep(f); + 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) { return 0; } // caller uses ftoa_rev + + if (exp) { + bin |= (npf_double_bin_t)0x1 << NPF_DOUBLE_MAN_BITS; + exp = (npf_ftoa_exp_t)(exp - NPF_DOUBLE_EXP_BIAS); + } else if (bin) { + exp = (npf_ftoa_exp_t)(1 - NPF_DOUBLE_EXP_BIAS); + } + + { int const n_frac_dig = (NPF_DOUBLE_MAN_BITS + 3) / 4; + int const prec = NPF_MIN(spec->prec, n_frac_dig); + int end, i; + + // Discard low nibbles and round (only constant shifts of 3 and 4) + { npf_double_bin_t carry = 0; + for (i = n_frac_dig - prec; i > 0; --i) { + carry = (bin >> 3) & 1; + bin >>= 4; + } + bin += carry; + } + + { npf_ftoa_exp_t const ae = (exp < 0) ? (npf_ftoa_exp_t)-exp : exp; + end = npf_utoa_rev((npf_uint_t)ae, buf, 10, 0); + buf[end++] = (exp < 0) ? '-' : '+'; + buf[end++] = (char)('P' + spec->case_adjust); + } + + for (i = 0; i < prec; ++i) { + int_fast8_t const d = (int_fast8_t)(bin & 0xF); + buf[end++] = (char)(((d < 10) ? '0' : ('A' - 10 + spec->case_adjust)) + d); + bin >>= 4; + } + + if (prec > 0 +#if NANOPRINTF_USE_ALT_FORM_FLAG == 1 + || spec->alt_form +#endif + ) { buf[end++] = '.'; } + + buf[end++] = (char)('0' + (int_fast8_t)(bin & 0xF)); + return end; + } +} + +#endif // NANOPRINTF_USE_FLOAT_HEX_FORMAT_SPECIFIER + +#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. + #ifdef _M_X64 + #define NPF_HAVE_BUILTIN_CLZ + #define NPF_CLZ _BitScanReverse64 + #elif NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 0 + #define NPF_HAVE_BUILTIN_CLZ + #define NPF_CLZ _BitScanReverse + #endif + #ifdef NPF_HAVE_BUILTIN_CLZ + unsigned long idx; + NPF_CLZ(&idx, u); + return (int)(idx + 1); + #endif +#elif NPF_CLANG || NPF_GCC_PAST_4_6 + #define NPF_HAVE_BUILTIN_CLZ + #if NPF_UINT_IS_WIDE + #define NPF_CLZ(X) ((sizeof(long long) * CHAR_BIT) - (size_t)__builtin_clzll(X)) + #else + #define NPF_CLZ(X) ((sizeof(long) * CHAR_BIT) - (size_t)__builtin_clzl(X)) + #endif + 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 + +static void npf_bufputc(int c, void *ctx) { + npf_bufputc_ctx_t *bpc = (npf_bufputc_ctx_t *)ctx; + // NULL dst -> count-only mode (size-query semantics). + if (bpc->dst && bpc->len) { --bpc->len; *bpc->dst++ = (char)c; } +} + +#define NPF_PUTC(VAL) do { pc((int)(VAL), pc_ctx); ++npf_n; } while (0) +#define NPF_PUT(VAL) do { pc((int)(VAL), pc_ctx); } while (0) + +#define NPF_EXTRACT(DST, MOD, CAST_TO, EXTRACT_AS) \ + case NPF_FMT_SPEC_LEN_MOD_##MOD: DST = (CAST_TO)va_arg(args, EXTRACT_AS); break + +// When sizeof(long) == sizeof(int) +// va_arg(*args, long) and va_arg(*args, int) read the same bits, so the LONG +// case can fall into the int-promotion default. +#if LONG_MAX == INT_MAX + #define NPF_LONG_IS_INT 1 +#else + #define NPF_LONG_IS_INT 0 +#endif + +int npf_vpprintf(npf_putc pc, void *pc_ctx, char const *format, va_list args) { + npf_format_spec_t fs; + char const *cur = format; + int npf_n = 0; + + while (*cur) { + char const *const fs_end = + (*cur != '%') ? 0 : npf_parse_format_spec_end(cur, &fs); + if (!fs_end) { NPF_PUTC(*cur++); continue; } + cur = fs_end; + + // 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 = va_arg(args, int); + 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 = va_arg(args, int); + if (fs.prec < 0) { fs.prec = 0; fs.prec_opt = NPF_FMT_SPEC_OPT_NONE; } + } +#endif + +#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1 + // Set default precision (we can do that only now that we have extracted the + // argument-provided precision (".*"), and know whether to ignore that or not. + if (fs.prec_opt == NPF_FMT_SPEC_OPT_NONE) { + if (fs.conv_spec == NPF_FMT_SPEC_CONV_POINTER) { + fs.prec = (sizeof(void *) * CHAR_BIT + 3) / 4; + } +#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1 + // float specs are last in the enum; a single range check identifies them. + else if (fs.conv_spec >= NPF_FMT_SPEC_CONV_FLOAT_DEC) { +#if NANOPRINTF_USE_FLOAT_HEX_FORMAT_SPECIFIER == 1 + fs.prec = (fs.conv_spec == NPF_FMT_SPEC_CONV_FLOAT_HEX) + ? (NPF_DOUBLE_MAN_BITS + 3) / 4 : 6; +#else + fs.prec = 6; +#endif + } +#endif + } +#endif + +#if (NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1) && \ + (NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1) + // For d i o u x X, the '0' flag must be ignored if a precision is provided. + // Those conversions are contiguous in the enum (except BINARY, b). + if ((fs.prec_opt != NPF_FMT_SPEC_OPT_NONE) && + (fs.conv_spec >= NPF_FMT_SPEC_CONV_SIGNED_INT) && + (fs.conv_spec <= NPF_FMT_SPEC_CONV_UNSIGNED_INT) +#if NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS == 1 + && (fs.conv_spec != NPF_FMT_SPEC_CONV_BINARY) +#endif + ) { fs.leading_zero_pad = 0; } +#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; + char 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 + uint_fast8_t zero = 0; +#endif +#endif + + // Extract and convert the argument to string, point cbuf at the text. + // Range checks for INT and FLOAT families avoid an 11-entry dispatch table. +#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1 + if (fs.conv_spec >= NPF_FMT_SPEC_CONV_FLOAT_DEC) { + npf_real_t val; +#if NANOPRINTF_USE_FLOAT_SINGLE_PRECISION == 1 + val = va_arg(args, npf_float_t).val; +#elif LDBL_MANT_DIG == DBL_MANT_DIG + // long double has the same representation as double + // no need to branch on the 'L' length modifier. + val = va_arg(args, double); +#else + if (fs.length_modifier == NPF_FMT_SPEC_LEN_MOD_LONG_DOUBLE) { + val = (npf_real_t)va_arg(args, long double); + } else { + val = va_arg(args, double); + } +#endif + + { npf_real_bin_t const b = npf_real_to_int_rep(val); + sign_c = (b >> NPF_REAL_SIGN_POS) ? '-' : fs.prepend; +#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1 + zero = !(b & ~((npf_real_bin_t)1 << NPF_REAL_SIGN_POS)); +#endif + } +#if NANOPRINTF_USE_FLOAT_HEX_FORMAT_SPECIFIER == 1 + if ((fs.conv_spec == NPF_FMT_SPEC_CONV_FLOAT_HEX) && + ((cbuf_len = npf_atoa_rev(cbuf, &fs, (double)val)) > 0)) { + need_0x = (char)('X' + fs.case_adjust); + } else +#endif + { cbuf_len = npf_ftoa_rev(cbuf, &fs, val); } + if (cbuf_len < 0) { // negative means text (not number), so ignore the '0' flag + cbuf_len = -cbuf_len; +#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1 + fs.leading_zero_pad = 0; +#endif + } + } else +#endif +#if NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS == 1 + if (fs.conv_spec == NPF_FMT_SPEC_CONV_WRITEBACK) { + switch (fs.length_modifier) { + case NPF_FMT_SPEC_LEN_MOD_NONE: *(va_arg(args, int *)) = npf_n; break; +#if NANOPRINTF_USE_SMALL_FORMAT_SPECIFIERS == 1 + case NPF_FMT_SPEC_LEN_MOD_SHORT: *(va_arg(args, short *)) = (short)npf_n; break; + case NPF_FMT_SPEC_LEN_MOD_CHAR: *(va_arg(args, signed char *)) = (signed char)npf_n; break; +#endif + case NPF_FMT_SPEC_LEN_MOD_LONG: *(va_arg(args, long *)) = (long)npf_n; break; +#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1 + case NPF_FMT_SPEC_LEN_MOD_LARGE_LONG_LONG: *(va_arg(args, long long *)) = (long long)npf_n; break; + case NPF_FMT_SPEC_LEN_MOD_LARGE_INTMAX: *(va_arg(args, intmax_t *)) = (intmax_t)npf_n; break; + case NPF_FMT_SPEC_LEN_MOD_LARGE_SIZET: *(va_arg(args, npf_ssize_t *)) = (npf_ssize_t)npf_n; break; + case NPF_FMT_SPEC_LEN_MOD_LARGE_PTRDIFFT: *(va_arg(args, ptrdiff_t *)) = (ptrdiff_t)npf_n; break; +#endif + default: break; + } + } else +#endif + if (fs.conv_spec >= NPF_FMT_SPEC_CONV_SIGNED_INT) { + npf_uint_t val; + uint_fast8_t base = 10u; + + if (fs.conv_spec == NPF_FMT_SPEC_CONV_SIGNED_INT) { + npf_int_t sval = 0; +#if !NPF_LONG_IS_INT || NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1 + switch (fs.length_modifier) { +#if !NPF_LONG_IS_INT + NPF_EXTRACT(sval, LONG, long, long); +#endif +#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1 + NPF_EXTRACT(sval, LARGE_LONG_LONG, long long, long long); + NPF_EXTRACT(sval, LARGE_INTMAX, intmax_t, intmax_t); + NPF_EXTRACT(sval, LARGE_SIZET, npf_ssize_t, npf_ssize_t); + NPF_EXTRACT(sval, LARGE_PTRDIFFT, ptrdiff_t, ptrdiff_t); +#endif + default: +#endif + { + int v = va_arg(args, int); +#if NANOPRINTF_USE_SMALL_FORMAT_SPECIFIERS == 1 + if (fs.length_modifier == NPF_FMT_SPEC_LEN_MOD_SHORT) { v = (short)v; } + else if (fs.length_modifier == NPF_FMT_SPEC_LEN_MOD_CHAR) { v = (signed char)v; } +#endif + sval = v; + } +#if !NPF_LONG_IS_INT || NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1 + break; + } +#endif + sign_c = (sval < 0) ? '-' : fs.prepend; + val = (npf_uint_t)sval; + if (sval < 0) { val = 0 - val; } + } else { + if (fs.conv_spec == NPF_FMT_SPEC_CONV_POINTER) { + val = (npf_uint_t)(uintptr_t)va_arg(args, void *); + base = 16u; + } else { +#if !NPF_LONG_IS_INT || NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1 + switch (fs.length_modifier) { +#if !NPF_LONG_IS_INT + NPF_EXTRACT(val, LONG, unsigned long, unsigned long); +#endif +#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1 + NPF_EXTRACT(val, LARGE_LONG_LONG, unsigned long long, unsigned long long); + NPF_EXTRACT(val, LARGE_INTMAX, uintmax_t, uintmax_t); + NPF_EXTRACT(val, LARGE_SIZET, size_t, size_t); + NPF_EXTRACT(val, LARGE_PTRDIFFT, npf_uptrdiff_t, npf_uptrdiff_t); +#endif + default: +#endif + { + unsigned v = va_arg(args, unsigned); +#if NANOPRINTF_USE_SMALL_FORMAT_SPECIFIERS == 1 + if (fs.length_modifier == NPF_FMT_SPEC_LEN_MOD_SHORT) { v = (unsigned short)v; } + else if (fs.length_modifier == NPF_FMT_SPEC_LEN_MOD_CHAR) { v = (unsigned char)v; } +#endif + val = v; + } +#if !NPF_LONG_IS_INT || NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1 + break; + } +#endif + if (fs.conv_spec == NPF_FMT_SPEC_CONV_OCTAL) { base = 8u; } + else if (fs.conv_spec == NPF_FMT_SPEC_CONV_HEX_INT) { base = 16u; } + } + } + +#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) { + // cbuf_len was initialized to 0; preserved here. +#if NANOPRINTF_USE_ALT_FORM_FLAG == 1 + if (base == 8u && fs.alt_form) { fs.prec = 1; } // octal '#' special +#endif + } 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 + { cbuf_len = npf_utoa_rev(val, cbuf, base, fs.case_adjust); } + +#if NANOPRINTF_USE_ALT_FORM_FLAG == 1 + if (val && fs.alt_form) { + if (base == 8u) { + cbuf[cbuf_len++] = '0'; + } else if (base == 16u) { + need_0x = (char)('X' + fs.case_adjust); + } +#if NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS == 1 + else if (fs.conv_spec == NPF_FMT_SPEC_CONV_BINARY) { + need_0x = (char)('B' + fs.case_adjust); + } +#endif + } +#endif + } + } else if (fs.conv_spec == NPF_FMT_SPEC_CONV_STRING) { + cbuf = va_arg(args, char *); +#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1 + for (char const *s = cbuf; + ((fs.prec_opt == NPF_FMT_SPEC_OPT_NONE) || (cbuf_len < fs.prec)) && cbuf && *s; + ++s, ++cbuf_len); +#else + for (char const *s = cbuf; cbuf && *s; ++s, ++cbuf_len); // strlen +#endif + } else { + // PERCENT or CHAR: produce a 1-char buffer. + *cbuf = (fs.conv_spec == NPF_FMT_SPEC_CONV_CHAR) ? (char)va_arg(args, int) : '%'; + cbuf_len = 1; + } + +#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1 + // Compute the field width pad character. '0' flag only with numeric types, + // '-' overrides '0', and a blank result (prec.0 with zero value) suppresses '0'. + // With no field width, field_pad clamps to 0 below, so pad_c is never used. + pad_c = ' '; + if (fs.leading_zero_pad && !fs.left_justified +#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1 + && !((fs.prec_opt != NPF_FMT_SPEC_OPT_NONE) && !fs.prec && zero) +#endif + ) { pad_c = '0'; } +#endif + + // Compute the number of bytes to truncate or '0'-pad. Skip for STRING + // (already handled by precision-limited length) and FLOAT (precision is + // after the decimal point; float specs are last in the enum). +#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1 + if ((fs.conv_spec != NPF_FMT_SPEC_CONV_STRING) +#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1 + && (fs.conv_spec < NPF_FMT_SPEC_CONV_FLOAT_DEC) +#endif + ) { prec_pad = NPF_MAX(0, fs.prec - cbuf_len); } +#endif + + // Total bytes this conversion emits; npf_n is bulk-updated at the end. + int spec_len = cbuf_len + !!sign_c + (need_0x ? 2 : 0) +#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1 + + prec_pad +#endif + ; + +#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1 + // Given the full converted length, how many pad bytes? + field_pad = fs.field_width - spec_len; + if (field_pad < 0) { field_pad = 0; } + spec_len += field_pad; + + // Right-justified padding: zero-pad goes AFTER sign/0x; space-pad goes BEFORE. +#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1 + // '0'-padding is contiguous with the leading precision zeros, so fold it into + // prec_pad; sign/0x is then emitted exactly once below. prec_pad stays 0 for + // STRING unless folded into, so the hoisted loop is a no-op there. + if (pad_c == '0') { + prec_pad += field_pad; + field_pad = 0; + } +#else + if (pad_c == '0') { + if (sign_c) { NPF_PUT(sign_c); sign_c = 0; } + if (need_0x) { NPF_PUT('0'); NPF_PUT(need_0x); need_0x = 0; } + } +#endif + if (!fs.left_justified) { + while (field_pad-- > 0) { NPF_PUT(pad_c); } + } +#endif + if (sign_c) { NPF_PUT(sign_c); } + if (need_0x) { NPF_PUT('0'); NPF_PUT(need_0x); } +#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1 + while (prec_pad-- > 0) { NPF_PUT('0'); } // leading zeros: precision and '0' pad +#endif + + // Write the converted payload. The STRING parse loop guarantees cbuf_len == 0 + // when cbuf is NULL, so the output loop can elide the `cbuf &&` check. + if (fs.conv_spec == NPF_FMT_SPEC_CONV_STRING) { + for (int i = 0; i < cbuf_len; ++i) { NPF_PUT(cbuf[i]); } + } else { +#if NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS == 1 + if (fs.conv_spec == NPF_FMT_SPEC_CONV_BINARY) { + while (cbuf_len) { NPF_PUT('0' + ((u.binval >> --cbuf_len) & 1)); } + } else +#endif + { while (cbuf_len-- > 0) { NPF_PUT(cbuf[cbuf_len]); } } // payload is reversed + } + +#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1 + // Apply left-justified field width. The right-justified loop above has + // already run field_pad below zero in the non-left-justified case, so + // this loop body only executes for left-justified specifiers. + while (field_pad-- > 0) { NPF_PUT(pad_c); } +#endif + // NPF_PUT emissions don't tally npf_n; add the conversion's total length in bulk. + npf_n += spec_len; + } + + return npf_n; +} + +#undef NPF_PUTC +#undef NPF_PUT +#undef NPF_EXTRACT +#undef NPF_LONG_IS_INT +#undef NPF_BIN_SHR +#undef NPF_BIN_SHL + +int npf_vsnprintf(char * NPF_RESTRICT buffer, + size_t bufsz, + char const * NPF_RESTRICT format, + va_list vlist) { + npf_bufputc_ctx_t bufputc_ctx = { buffer, bufsz }; + int const n = npf_vpprintf(npf_bufputc, &bufputc_ctx, format, vlist); + + if (buffer && bufsz) { + // npf_vpprintf never returns negative (no encoding errors possible). +#ifdef NANOPRINTF_SNPRINTF_SAFE_EMPTY_STRING_ON_OVERFLOW + buffer[(unsigned)n >= bufsz ? 0 : (unsigned)n] = '\0'; +#else + buffer[NPF_MIN((unsigned)n, bufsz - 1)] = '\0'; +#endif + } + + return n; +} + +int npf_pprintf_(npf_putc pc, + void * NPF_RESTRICT pc_ctx, + char const * NPF_RESTRICT format, + ...) { + va_list val; + va_start(val, format); + int const rv = npf_vpprintf(pc, pc_ctx, format, val); + va_end(val); + return rv; +} + +int npf_snprintf_(char * NPF_RESTRICT buffer, + size_t bufsz, + const char * NPF_RESTRICT format, + ...) { + va_list val; + va_start(val, format); + int const rv = npf_vsnprintf(buffer, bufsz, format, val); + va_end(val); + return rv; +} + +#if NPF_HAVE_GCC_WARNING_PRAGMAS + #pragma GCC diagnostic pop +#endif + +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +#endif // NPF_IMPLEMENTATION_INCLUDED +#endif // NANOPRINTF_IMPLEMENTATION + +// Single-precision argument wrapping and MAP macro expansion machinery. +// The npf_snprintf / npf_pprintf / NPF_MAP_ARGS macros defined above reference +// these, but that's fine: macro bodies are only expanded at the point of +// invocation, not at the point of definition. + +#ifndef NPF_MAP_INCLUDED +#define NPF_MAP_INCLUDED + +#if defined(NANOPRINTF_USE_FLOAT_SINGLE_PRECISION) && \ + (NANOPRINTF_USE_FLOAT_SINGLE_PRECISION == 1) + +// NPF__WRAP: wrap float/double args into npf_float_t, pass other types through. +// C++ uses function overloading; C uses _Generic with function-pointer selection. +// _Generic picks a function, then (x) calls it. Non-selected branches are bare +// function names (always valid), sidestepping _Generic's type-check-all-branches +// behavior that would reject (float)(string_ptr) in non-selected branches. +#if defined(__cplusplus) + extern "C++" { + static inline npf_float_t npf__wrap_impl(float f) { + npf_float_t r; r.val = f; return r; + } + static inline npf_float_t npf__wrap_impl(double d) { + npf_float_t r; r.val = (float)d; return r; + } + template static inline T npf__wrap_impl(T v) { return v; } + } + #define NPF__WRAP(x) npf__wrap_impl(x) +#elif defined(__GNUC__) || defined(__clang__) + #define NPF__IS_REAL(x) \ + (__builtin_types_compatible_p(__typeof__(0 ? (x) : (x)), float) || \ + __builtin_types_compatible_p(__typeof__(0 ? (x) : (x)), double)) + #define NPF__WRAP(x) __builtin_choose_expr(NPF__IS_REAL(x), \ + ({ npf_float_t _npf_r; \ + _npf_r.val = (float)__builtin_choose_expr(NPF__IS_REAL(x), (x), 0); \ + _npf_r; }), \ + (x)) +#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) + static inline npf_float_t npf__wf(float f) { + npf_float_t r; r.val = f; return r; + } + static inline npf_float_t npf__wd(double d) { + npf_float_t r; r.val = (float)d; return r; + } + static inline npf_float_t npf__wld(long double d) { + npf_float_t r; r.val = (float)d; return r; + } + + static inline int npf__id_i(int v) { return v; } + static inline unsigned npf__id_u(unsigned v) { return v; } + static inline long npf__id_l(long v) { return v; } + static inline unsigned long npf__id_ul(unsigned long v) { return v; } + static inline long long npf__id_ll(long long v) { return v; } + static inline unsigned long long npf__id_ull(unsigned long long v) { return v; } + static inline char npf__id_c(char v) { return v; } + static inline signed char npf__id_sc(signed char v) { return v; } + static inline unsigned char npf__id_uc(unsigned char v) { return v; } + static inline short npf__id_s(short v) { return v; } + static inline unsigned short npf__id_us(unsigned short v) { return v; } + static inline char *npf__id_cp(char *v) { return v; } + static inline char const *npf__id_ccp(char const *v) { return v; } + static inline void *npf__id_vp(void *v) { return v; } + static inline void const *npf__id_cvp(void const *v) { return v; } + static inline int *npf__id_ip(int *v) { return v; } + static inline short *npf__id_sp(short *v) { return v; } + static inline long *npf__id_lp(long *v) { return v; } + static inline long long *npf__id_llp(long long *v) { return v; } + static inline signed char *npf__id_scp(signed char *v) { return v; } + + #define NPF__WRAP(x) _Generic((x), \ + float: npf__wf, \ + double: npf__wd, \ + long double: npf__wld, \ + int: npf__id_i, \ + unsigned: npf__id_u, \ + long: npf__id_l, \ + unsigned long: npf__id_ul, \ + long long: npf__id_ll, \ + unsigned long long: npf__id_ull, \ + char: npf__id_c, \ + signed char: npf__id_sc, \ + unsigned char: npf__id_uc, \ + short: npf__id_s, \ + unsigned short: npf__id_us, \ + char *: npf__id_cp, \ + char const *: npf__id_ccp, \ + void *: npf__id_vp, \ + void const *: npf__id_cvp, \ + int *: npf__id_ip, \ + short *: npf__id_sp, \ + long *: npf__id_lp, \ + long long *: npf__id_llp, \ + signed char *: npf__id_scp)(x) +#else + #error Single-precision float wrapping requires C11, GCC/Clang, or C++. +#endif + +// Argument counting (up to 64 variadic args) +#define NPF__NARG(...) NPF__NARG_(__VA_ARGS__, NPF__RSEQ()) +#define NPF__NARG_(...) NPF__ARG_N(__VA_ARGS__) +#define NPF__ARG_N( \ + _1, _2, _3, _4, _5, _6, _7, _8, _9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \ + _21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \ + _41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \ + _61,_62,_63,_64,N,...) N +#define NPF__RSEQ() \ + 64,63,62,61,60,59,58,57,56,55,54,53,52,51,50,49,48,47,46,45,44,43,42,41,40,39,38, \ + 37,36,35,34,33,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11, \ + 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 + +// Token pasting +#define NPF__CAT(a,b) NPF__CAT_(a,b) +#define NPF__CAT_(a,b) a##b + +// MAP: apply f to each argument +#define NPF__MAP(f,...) NPF__CAT(NPF__MAP_,NPF__NARG(__VA_ARGS__))(f,__VA_ARGS__) +#define NPF__MAP_1(f,a) f(a) +#define NPF__MAP_2(f,a,...) f(a), NPF__MAP_1(f,__VA_ARGS__) +#define NPF__MAP_3(f,a,...) f(a), NPF__MAP_2(f,__VA_ARGS__) +#define NPF__MAP_4(f,a,...) f(a), NPF__MAP_3(f,__VA_ARGS__) +#define NPF__MAP_5(f,a,...) f(a), NPF__MAP_4(f,__VA_ARGS__) +#define NPF__MAP_6(f,a,...) f(a), NPF__MAP_5(f,__VA_ARGS__) +#define NPF__MAP_7(f,a,...) f(a), NPF__MAP_6(f,__VA_ARGS__) +#define NPF__MAP_8(f,a,...) f(a), NPF__MAP_7(f,__VA_ARGS__) +#define NPF__MAP_9(f,a,...) f(a), NPF__MAP_8(f,__VA_ARGS__) +#define NPF__MAP_10(f,a,...) f(a), NPF__MAP_9(f,__VA_ARGS__) +#define NPF__MAP_11(f,a,...) f(a), NPF__MAP_10(f,__VA_ARGS__) +#define NPF__MAP_12(f,a,...) f(a), NPF__MAP_11(f,__VA_ARGS__) +#define NPF__MAP_13(f,a,...) f(a), NPF__MAP_12(f,__VA_ARGS__) +#define NPF__MAP_14(f,a,...) f(a), NPF__MAP_13(f,__VA_ARGS__) +#define NPF__MAP_15(f,a,...) f(a), NPF__MAP_14(f,__VA_ARGS__) +#define NPF__MAP_16(f,a,...) f(a), NPF__MAP_15(f,__VA_ARGS__) +#define NPF__MAP_17(f,a,...) f(a), NPF__MAP_16(f,__VA_ARGS__) +#define NPF__MAP_18(f,a,...) f(a), NPF__MAP_17(f,__VA_ARGS__) +#define NPF__MAP_19(f,a,...) f(a), NPF__MAP_18(f,__VA_ARGS__) +#define NPF__MAP_20(f,a,...) f(a), NPF__MAP_19(f,__VA_ARGS__) +#define NPF__MAP_21(f,a,...) f(a), NPF__MAP_20(f,__VA_ARGS__) +#define NPF__MAP_22(f,a,...) f(a), NPF__MAP_21(f,__VA_ARGS__) +#define NPF__MAP_23(f,a,...) f(a), NPF__MAP_22(f,__VA_ARGS__) +#define NPF__MAP_24(f,a,...) f(a), NPF__MAP_23(f,__VA_ARGS__) +#define NPF__MAP_25(f,a,...) f(a), NPF__MAP_24(f,__VA_ARGS__) +#define NPF__MAP_26(f,a,...) f(a), NPF__MAP_25(f,__VA_ARGS__) +#define NPF__MAP_27(f,a,...) f(a), NPF__MAP_26(f,__VA_ARGS__) +#define NPF__MAP_28(f,a,...) f(a), NPF__MAP_27(f,__VA_ARGS__) +#define NPF__MAP_29(f,a,...) f(a), NPF__MAP_28(f,__VA_ARGS__) +#define NPF__MAP_30(f,a,...) f(a), NPF__MAP_29(f,__VA_ARGS__) +#define NPF__MAP_31(f,a,...) f(a), NPF__MAP_30(f,__VA_ARGS__) +#define NPF__MAP_32(f,a,...) f(a), NPF__MAP_31(f,__VA_ARGS__) +#define NPF__MAP_33(f,a,...) f(a), NPF__MAP_32(f,__VA_ARGS__) +#define NPF__MAP_34(f,a,...) f(a), NPF__MAP_33(f,__VA_ARGS__) +#define NPF__MAP_35(f,a,...) f(a), NPF__MAP_34(f,__VA_ARGS__) +#define NPF__MAP_36(f,a,...) f(a), NPF__MAP_35(f,__VA_ARGS__) +#define NPF__MAP_37(f,a,...) f(a), NPF__MAP_36(f,__VA_ARGS__) +#define NPF__MAP_38(f,a,...) f(a), NPF__MAP_37(f,__VA_ARGS__) +#define NPF__MAP_39(f,a,...) f(a), NPF__MAP_38(f,__VA_ARGS__) +#define NPF__MAP_40(f,a,...) f(a), NPF__MAP_39(f,__VA_ARGS__) +#define NPF__MAP_41(f,a,...) f(a), NPF__MAP_40(f,__VA_ARGS__) +#define NPF__MAP_42(f,a,...) f(a), NPF__MAP_41(f,__VA_ARGS__) +#define NPF__MAP_43(f,a,...) f(a), NPF__MAP_42(f,__VA_ARGS__) +#define NPF__MAP_44(f,a,...) f(a), NPF__MAP_43(f,__VA_ARGS__) +#define NPF__MAP_45(f,a,...) f(a), NPF__MAP_44(f,__VA_ARGS__) +#define NPF__MAP_46(f,a,...) f(a), NPF__MAP_45(f,__VA_ARGS__) +#define NPF__MAP_47(f,a,...) f(a), NPF__MAP_46(f,__VA_ARGS__) +#define NPF__MAP_48(f,a,...) f(a), NPF__MAP_47(f,__VA_ARGS__) +#define NPF__MAP_49(f,a,...) f(a), NPF__MAP_48(f,__VA_ARGS__) +#define NPF__MAP_50(f,a,...) f(a), NPF__MAP_49(f,__VA_ARGS__) +#define NPF__MAP_51(f,a,...) f(a), NPF__MAP_50(f,__VA_ARGS__) +#define NPF__MAP_52(f,a,...) f(a), NPF__MAP_51(f,__VA_ARGS__) +#define NPF__MAP_53(f,a,...) f(a), NPF__MAP_52(f,__VA_ARGS__) +#define NPF__MAP_54(f,a,...) f(a), NPF__MAP_53(f,__VA_ARGS__) +#define NPF__MAP_55(f,a,...) f(a), NPF__MAP_54(f,__VA_ARGS__) +#define NPF__MAP_56(f,a,...) f(a), NPF__MAP_55(f,__VA_ARGS__) +#define NPF__MAP_57(f,a,...) f(a), NPF__MAP_56(f,__VA_ARGS__) +#define NPF__MAP_58(f,a,...) f(a), NPF__MAP_57(f,__VA_ARGS__) +#define NPF__MAP_59(f,a,...) f(a), NPF__MAP_58(f,__VA_ARGS__) +#define NPF__MAP_60(f,a,...) f(a), NPF__MAP_59(f,__VA_ARGS__) +#define NPF__MAP_61(f,a,...) f(a), NPF__MAP_60(f,__VA_ARGS__) +#define NPF__MAP_62(f,a,...) f(a), NPF__MAP_61(f,__VA_ARGS__) +#define NPF__MAP_63(f,a,...) f(a), NPF__MAP_62(f,__VA_ARGS__) +#define NPF__MAP_64(f,a,...) f(a), NPF__MAP_63(f,__VA_ARGS__) + +#endif // NANOPRINTF_USE_FLOAT_SINGLE_PRECISION + +#endif // NPF_MAP_INCLUDED + +/* + 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. +*/ diff --git a/compat/stdio/nanoprintf_impl.c b/compat/stdio/nanoprintf_impl.c new file mode 100644 index 000000000..0903bcde9 --- /dev/null +++ b/compat/stdio/nanoprintf_impl.c @@ -0,0 +1,2 @@ +#define NANOPRINTF_IMPLEMENTATION +#include "nanoprintf.h" diff --git a/compat/stdio/stdio.h b/compat/stdio/stdio.h new file mode 100644 index 000000000..09840ce89 --- /dev/null +++ b/compat/stdio/stdio.h @@ -0,0 +1,9 @@ +#ifndef _BS_STDIO_H_ +#define _BS_STDIO_H_ + +#include_next + +#include "nanoprintf.h" +#define snprintf npf_vsnprintf + +#endif /* _BS_STDIO_H_ */ From ef7b02af99fd71ff7cc0878df2140d09daa80b4a Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 08:38:23 -0400 Subject: [PATCH 05/57] stdbool check --- compat/configure.cmd | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/compat/configure.cmd b/compat/configure.cmd index bdb87f6dc..b1f1397f5 100644 --- a/compat/configure.cmd +++ b/compat/configure.cmd @@ -11,6 +11,19 @@ cd /d "%~dp0" if not exist tmp mkdir tmp type nul > tmp\config.log +> config.bat echo @echo off + +>tmp\test.c echo.#include ^ +>>tmp\test.c echo.int main(void^){return 0;} + +echo.checking for stdbool.h: >> tmp\config.log +%CC% /nologo /Oi- tmp\test.c /c /Fo:tmp\test.obj >> tmp\config.log 2>&1 +if %errorlevel% neq 0 ( + echo checking for stdbool.h: no + >>config.bat echo set INCLUDES=%%INCLUDES%% /Icompat\stdbool +) else ( + echo checking for stdbool.h: yes +) >tmp\test.c echo.#include ^ >>tmp\test.c echo.int main(void^){ @@ -22,10 +35,9 @@ echo.checking for snprintf: >> tmp\config.log %CC% /nologo /Oi- tmp\test.c /c /Fo:tmp\test.obj >> tmp\config.log 2>&1 if %errorlevel% neq 0 ( echo checking for snprintf: no - > config.bat echo @echo off - >> config.bat echo set DEFINES=%%DEFINES%% /DNO_SNPRINTF - >> config.bat echo set INCLUDES=%%INCLUDES%% /Icompat\stdio - >> config.bat echo set SRCS=%%SRCS%% compat\stdio\nanoprintf_impl.c + >>config.bat echo set DEFINES=%%DEFINES%% /DNO_SNPRINTF + >>config.bat echo set INCLUDES=%%INCLUDES%% /Icompat\stdio + >>config.bat echo set SRCS=%%SRCS%% compat\stdio\nanoprintf_impl.c ) else ( echo checking for snprintf: yes ) From 894583d000cc66e8cc6becdc76cbc7a5142ebee1 Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 08:40:17 -0400 Subject: [PATCH 06/57] better --- compat/configure.cmd | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compat/configure.cmd b/compat/configure.cmd index b1f1397f5..ac5709aea 100644 --- a/compat/configure.cmd +++ b/compat/configure.cmd @@ -16,13 +16,13 @@ type nul > tmp\config.log >tmp\test.c echo.#include ^ >>tmp\test.c echo.int main(void^){return 0;} -echo.checking for stdbool.h: >> tmp\config.log +echo.checking if stdbool.h works: >> tmp\config.log %CC% /nologo /Oi- tmp\test.c /c /Fo:tmp\test.obj >> tmp\config.log 2>&1 if %errorlevel% neq 0 ( - echo checking for stdbool.h: no + echo checking if stdbool.h works: no >>config.bat echo set INCLUDES=%%INCLUDES%% /Icompat\stdbool ) else ( - echo checking for stdbool.h: yes + echo checking if stdbool.h works: yes ) >tmp\test.c echo.#include ^ From 9678470ddb0a0d86e24c88fa58381554c4b6afd9 Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 08:43:02 -0400 Subject: [PATCH 07/57] check function --- compat/configure.cmd | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/compat/configure.cmd b/compat/configure.cmd index ac5709aea..ce35c6291 100644 --- a/compat/configure.cmd +++ b/compat/configure.cmd @@ -15,14 +15,9 @@ type nul > tmp\config.log >tmp\test.c echo.#include ^ >>tmp\test.c echo.int main(void^){return 0;} - -echo.checking if stdbool.h works: >> tmp\config.log -%CC% /nologo /Oi- tmp\test.c /c /Fo:tmp\test.obj >> tmp\config.log 2>&1 -if %errorlevel% neq 0 ( - echo checking if stdbool.h works: no +call :check if stdbool.h works +if errorlevel 1 ( >>config.bat echo set INCLUDES=%%INCLUDES%% /Icompat\stdbool -) else ( - echo checking if stdbool.h works: yes ) >tmp\test.c echo.#include ^ @@ -30,16 +25,23 @@ if %errorlevel% neq 0 ( >>tmp\test.c echo. char buf[64]; >>tmp\test.c echo. return snprintf(buf, sizeof(buf^), "test"^); >>tmp\test.c echo.} - -echo.checking for snprintf: >> tmp\config.log -%CC% /nologo /Oi- tmp\test.c /c /Fo:tmp\test.obj >> tmp\config.log 2>&1 -if %errorlevel% neq 0 ( - echo checking for snprintf: no +call :check for snprintf +if errorlevel 1 ( >>config.bat echo set DEFINES=%%DEFINES%% /DNO_SNPRINTF >>config.bat echo set INCLUDES=%%INCLUDES%% /Icompat\stdio >>config.bat echo set SRCS=%%SRCS%% compat\stdio\nanoprintf_impl.c -) else ( - echo checking for snprintf: yes ) del tmp\test.c tmp\test.obj 2>nul +exit /b 0 + +:check +echo.checking %* >> tmp\config.log +%CC% /nologo /Oi- tmp\test.c /c /Fo:tmp\test.obj >> tmp\config.log 2>&1 +if %errorlevel% equ 0 ( + echo checking %*: yes + exit /b 0 +) else ( + echo checking %*: no + exit /b 1 +) From 59642901f5c3d97e92acaa1d7a68bc0ea7f7c5f1 Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 08:48:17 -0400 Subject: [PATCH 08/57] compat stdio --- compat/stdio/stdio.h | 9 --------- src/android/main.c | 2 +- src/audio/miniaudio/ma_audio_system.c | 2 +- src/audio/openal/al_audio_system.c | 2 +- src/audio/openal/wave.c | 2 +- src/audio/ps2/ps2_audio_system.c | 2 +- src/audio/ps2/ps2_audio_system.h | 2 +- src/audio/web/web_audio_system.c | 2 +- src/binary_reader.h | 2 +- src/data_win.c | 2 +- src/data_win.h | 2 +- src/data_win_print.c | 2 +- src/desktop/backends/glfw2.c | 2 +- src/desktop/backends/glfw3.c | 2 +- src/desktop/backends/sdl1.c | 2 +- src/desktop/backends/sdl2.c | 2 +- src/desktop/backends/sdl3.c | 2 +- src/desktop/main.c | 2 +- src/event_table.c | 2 +- src/gl/gl_renderer.c | 2 +- src/gl_common/gl_common.c | 2 +- src/gl_legacy/gl_legacy_renderer.c | 2 +- src/gml_array.c | 2 +- src/image/image_decoder.c | 2 +- src/ini.c | 2 +- src/input_recording.c | 2 +- src/json_reader.c | 2 +- src/json_writer.c | 2 +- src/overlay_file_system.c | 2 +- src/profiler.c | 2 +- src/profiler.h | 2 +- src/ps2/debug_font_renderer.c | 2 +- src/ps2/gs_renderer.c | 2 +- src/ps2/main.c | 2 +- src/ps2/ps2_file_system.c | 2 +- src/ps2/ps2_gamepad.c | 2 +- src/ps2/ps2_overlay.c | 2 +- src/ps2/ps2_utils.c | 2 +- src/ps3/main.c | 2 +- src/ps3/ps3_overlay.c | 2 +- src/ps3/ps3_textures.c | 2 +- src/renderer.h | 2 +- src/runner.c | 2 +- src/runner_mouse.c | 2 +- src/rvalue.h | 2 +- src/stdio_compat.h | 11 +++++++++++ src/string_builder.c | 2 +- src/utils.h | 2 +- src/vm.c | 2 +- src/vm_builtins.c | 2 +- src/web-meta/main.c | 2 +- src/web/main.c | 2 +- 52 files changed, 61 insertions(+), 59 deletions(-) delete mode 100644 compat/stdio/stdio.h create mode 100644 src/stdio_compat.h diff --git a/compat/stdio/stdio.h b/compat/stdio/stdio.h deleted file mode 100644 index 09840ce89..000000000 --- a/compat/stdio/stdio.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef _BS_STDIO_H_ -#define _BS_STDIO_H_ - -#include_next - -#include "nanoprintf.h" -#define snprintf npf_vsnprintf - -#endif /* _BS_STDIO_H_ */ diff --git a/src/android/main.c b/src/android/main.c index 741eb4df9..4b5caa293 100644 --- a/src/android/main.c +++ b/src/android/main.c @@ -1,5 +1,5 @@ #include -#include +#include "stdio_compat.h" #include #include #include diff --git a/src/audio/miniaudio/ma_audio_system.c b/src/audio/miniaudio/ma_audio_system.c index 3ec79ec7c..9c8a146c9 100644 --- a/src/audio/miniaudio/ma_audio_system.c +++ b/src/audio/miniaudio/ma_audio_system.c @@ -22,7 +22,7 @@ #include "data_win.h" #include "utils.h" -#include +#include "stdio_compat.h" #include #include #include "stb_ds.h" diff --git a/src/audio/openal/al_audio_system.c b/src/audio/openal/al_audio_system.c index a2223b03f..ee8dbf2a9 100644 --- a/src/audio/openal/al_audio_system.c +++ b/src/audio/openal/al_audio_system.c @@ -10,7 +10,7 @@ #include "utils.h" #include "wave.h" -#include +#include "stdio_compat.h" #include #include #include "stb_ds.h" diff --git a/src/audio/openal/wave.c b/src/audio/openal/wave.c index 032e2b29c..636dcc4ca 100644 --- a/src/audio/openal/wave.c +++ b/src/audio/openal/wave.c @@ -1,7 +1,7 @@ // From https://gist.github.com/SteelPh0enix/e44d4a030dd8816309af84809ed75604 #include "wave.h" -#include +#include "stdio_compat.h" #include #include "binary_utils.h" #include "utils.h" diff --git a/src/audio/ps2/ps2_audio_system.c b/src/audio/ps2/ps2_audio_system.c index 29bf9fa7f..10a615a04 100644 --- a/src/audio/ps2/ps2_audio_system.c +++ b/src/audio/ps2/ps2_audio_system.c @@ -2,7 +2,7 @@ #include "ps2/ps2_utils.h" #include "utils.h" -#include +#include "stdio_compat.h" #include #include #include diff --git a/src/audio/ps2/ps2_audio_system.h b/src/audio/ps2/ps2_audio_system.h index 6d83e27f3..fed80172d 100644 --- a/src/audio/ps2/ps2_audio_system.h +++ b/src/audio/ps2/ps2_audio_system.h @@ -6,7 +6,7 @@ #include #include -#include +#include "stdio_compat.h" #define MAX_PS2_SOUND_INSTANCES 64 #define PS2_SOUND_INSTANCE_ID_BASE 100000 diff --git a/src/audio/web/web_audio_system.c b/src/audio/web/web_audio_system.c index 479913222..cb14f42d1 100644 --- a/src/audio/web/web_audio_system.c +++ b/src/audio/web/web_audio_system.c @@ -6,7 +6,7 @@ #include "data_win.h" #include "utils.h" -#include +#include "stdio_compat.h" #include #include #include "stb_ds.h" diff --git a/src/binary_reader.h b/src/binary_reader.h index 2dcd4cf43..b9ae09a91 100644 --- a/src/binary_reader.h +++ b/src/binary_reader.h @@ -4,7 +4,7 @@ #include "common.h" #include #include -#include +#include "stdio_compat.h" #include typedef struct { diff --git a/src/data_win.c b/src/data_win.c index 5dd673ffe..878182ddb 100644 --- a/src/data_win.c +++ b/src/data_win.c @@ -2,7 +2,7 @@ #include "binary_reader.h" #include -#include +#include "stdio_compat.h" #include #include #include "math_compat.h" diff --git a/src/data_win.h b/src/data_win.h index 49633b96e..c38f784eb 100644 --- a/src/data_win.h +++ b/src/data_win.h @@ -5,7 +5,7 @@ #include #include #include -#include +#include "stdio_compat.h" #include #include "utils.h" diff --git a/src/data_win_print.c b/src/data_win_print.c index fc357e198..900f4e3db 100644 --- a/src/data_win_print.c +++ b/src/data_win_print.c @@ -1,6 +1,6 @@ #include "data_win.h" -#include +#include "stdio_compat.h" #include #include "utils.h" diff --git a/src/desktop/backends/glfw2.c b/src/desktop/backends/glfw2.c index 8debf98dc..e167674f3 100644 --- a/src/desktop/backends/glfw2.c +++ b/src/desktop/backends/glfw2.c @@ -1,4 +1,4 @@ -#include +#include "stdio_compat.h" #include #ifdef _WIN32 diff --git a/src/desktop/backends/glfw3.c b/src/desktop/backends/glfw3.c index 28cba4c2b..6d85924d8 100644 --- a/src/desktop/backends/glfw3.c +++ b/src/desktop/backends/glfw3.c @@ -1,5 +1,5 @@ #include -#include +#include "stdio_compat.h" #include #include "math_compat.h" diff --git a/src/desktop/backends/sdl1.c b/src/desktop/backends/sdl1.c index c416ab16b..766cc0c8d 100644 --- a/src/desktop/backends/sdl1.c +++ b/src/desktop/backends/sdl1.c @@ -1,6 +1,6 @@ #include #include -#include +#include "stdio_compat.h" #include #include diff --git a/src/desktop/backends/sdl2.c b/src/desktop/backends/sdl2.c index 97f301d76..ca4ca9828 100644 --- a/src/desktop/backends/sdl2.c +++ b/src/desktop/backends/sdl2.c @@ -1,5 +1,5 @@ #include -#include +#include "stdio_compat.h" #include diff --git a/src/desktop/backends/sdl3.c b/src/desktop/backends/sdl3.c index 7cf229eb8..e1895790f 100644 --- a/src/desktop/backends/sdl3.c +++ b/src/desktop/backends/sdl3.c @@ -1,5 +1,5 @@ #include -#include +#include "stdio_compat.h" #include diff --git a/src/desktop/main.c b/src/desktop/main.c index 085830abc..0b0df3af4 100644 --- a/src/desktop/main.c +++ b/src/desktop/main.c @@ -5,7 +5,7 @@ #include "platformdefs.h" #include -#include +#include "stdio_compat.h" #include #include #include diff --git a/src/event_table.c b/src/event_table.c index 74bccab96..a8768ff7d 100644 --- a/src/event_table.c +++ b/src/event_table.c @@ -1,6 +1,6 @@ #include "event_table.h" #include "utils.h" -#include +#include "stdio_compat.h" #include #include diff --git a/src/gl/gl_renderer.c b/src/gl/gl_renderer.c index 1cc5cb727..49bc43a92 100644 --- a/src/gl/gl_renderer.c +++ b/src/gl/gl_renderer.c @@ -7,7 +7,7 @@ #else #include #endif -#include +#include "stdio_compat.h" #include #include #include "math_compat.h" diff --git a/src/gl_common/gl_common.c b/src/gl_common/gl_common.c index 4819b35bd..a13b4e52e 100644 --- a/src/gl_common/gl_common.c +++ b/src/gl_common/gl_common.c @@ -1,6 +1,6 @@ #include "gl_common.h" -#include +#include "stdio_compat.h" #include #include diff --git a/src/gl_legacy/gl_legacy_renderer.c b/src/gl_legacy/gl_legacy_renderer.c index baf6f9211..b55b1ce8c 100644 --- a/src/gl_legacy/gl_legacy_renderer.c +++ b/src/gl_legacy/gl_legacy_renderer.c @@ -33,7 +33,7 @@ extern GLint gPalettedUPaletteVLoc; #define PS3_PALETTED_BEGIN(tpagIndex) ((void)0) #define PS3_PALETTED_END() ((void)0) #endif -#include +#include "stdio_compat.h" #include #include #include "math_compat.h" diff --git a/src/gml_array.c b/src/gml_array.c index 21fd35029..555e0d66d 100644 --- a/src/gml_array.c +++ b/src/gml_array.c @@ -1,7 +1,7 @@ #include "gml_array.h" #include "rvalue.h" #include "utils.h" -#include +#include "stdio_compat.h" #include #include diff --git a/src/image/image_decoder.c b/src/image/image_decoder.c index 16b2aacc9..3956a4955 100644 --- a/src/image/image_decoder.c +++ b/src/image/image_decoder.c @@ -1,6 +1,6 @@ #include "image_decoder.h" -#include +#include "stdio_compat.h" #include #include #include diff --git a/src/ini.c b/src/ini.c index 44afa5fb4..d8da7429d 100644 --- a/src/ini.c +++ b/src/ini.c @@ -1,7 +1,7 @@ #include "ini.h" #include "utils.h" -#include +#include "stdio_compat.h" #include #include diff --git a/src/input_recording.c b/src/input_recording.c index 51ec4623c..8a24a924d 100644 --- a/src/input_recording.c +++ b/src/input_recording.c @@ -4,7 +4,7 @@ #include "utils.h" -#include +#include "stdio_compat.h" #include #include diff --git a/src/json_reader.c b/src/json_reader.c index c863648ae..b1ccf33c3 100644 --- a/src/json_reader.c +++ b/src/json_reader.c @@ -1,6 +1,6 @@ #include "json_reader.h" -#include +#include "stdio_compat.h" #include #include diff --git a/src/json_writer.c b/src/json_writer.c index 0612b2ea0..2c1a8ebe3 100644 --- a/src/json_writer.c +++ b/src/json_writer.c @@ -1,7 +1,7 @@ #include "json_writer.h" #include "utils.h" -#include +#include "stdio_compat.h" #include #include diff --git a/src/overlay_file_system.c b/src/overlay_file_system.c index e489e0728..0fd3cd97d 100644 --- a/src/overlay_file_system.c +++ b/src/overlay_file_system.c @@ -2,7 +2,7 @@ #include "utils.h" #include "stb_ds.h" -#include +#include "stdio_compat.h" #include #include #include diff --git a/src/profiler.c b/src/profiler.c index 97026066f..5a76f49b5 100644 --- a/src/profiler.c +++ b/src/profiler.c @@ -2,7 +2,7 @@ #include #include -#include +#include "stdio_compat.h" #include "utils.h" #include "stb_ds.h" diff --git a/src/profiler.h b/src/profiler.h index 99bd87cc2..3e011680c 100644 --- a/src/profiler.h +++ b/src/profiler.h @@ -3,7 +3,7 @@ #include #include -#include +#include "stdio_compat.h" // GML script profiler. // Tracks self-time (exclusive of nested script calls) and self-instruction-count per code name. diff --git a/src/ps2/debug_font_renderer.c b/src/ps2/debug_font_renderer.c index 02ec124a9..e2036596e 100644 --- a/src/ps2/debug_font_renderer.c +++ b/src/ps2/debug_font_renderer.c @@ -8,7 +8,7 @@ #include #include #include -#include +#include "stdio_compat.h" #include #include diff --git a/src/ps2/gs_renderer.c b/src/ps2/gs_renderer.c index 75cbbea33..388b5a1ca 100644 --- a/src/ps2/gs_renderer.c +++ b/src/ps2/gs_renderer.c @@ -7,7 +7,7 @@ #include #include -#include +#include "stdio_compat.h" #include #include diff --git a/src/ps2/main.c b/src/ps2/main.c index 1a13127d6..5ae4a9f7d 100644 --- a/src/ps2/main.c +++ b/src/ps2/main.c @@ -1,7 +1,7 @@ #include #include #include -#include +#include "stdio_compat.h" #include #include #include diff --git a/src/ps2/ps2_file_system.c b/src/ps2/ps2_file_system.c index cf7e4412b..9e9cddf0b 100644 --- a/src/ps2/ps2_file_system.c +++ b/src/ps2/ps2_file_system.c @@ -3,7 +3,7 @@ #include "../json_reader.h" #include "../utils.h" -#include +#include "stdio_compat.h" #include #include #include diff --git a/src/ps2/ps2_gamepad.c b/src/ps2/ps2_gamepad.c index 1c2aaa4aa..b7527a0f1 100644 --- a/src/ps2/ps2_gamepad.c +++ b/src/ps2/ps2_gamepad.c @@ -2,7 +2,7 @@ #include "ps2_gamepad.h" #include -#include +#include "stdio_compat.h" #include // Track DualShock-mode handshake completion per port so poll() can lazily kick it off if it hasn't run yet. diff --git a/src/ps2/ps2_overlay.c b/src/ps2/ps2_overlay.c index 4c04b8809..a45c4d0f3 100644 --- a/src/ps2/ps2_overlay.c +++ b/src/ps2/ps2_overlay.c @@ -1,7 +1,7 @@ #include "ps2_overlay.h" #include -#include +#include "stdio_compat.h" #include #include "ps2_utils.h" diff --git a/src/ps2/ps2_utils.c b/src/ps2/ps2_utils.c index bdb0021d9..2f0778339 100644 --- a/src/ps2/ps2_utils.c +++ b/src/ps2/ps2_utils.c @@ -1,4 +1,4 @@ -#include +#include "stdio_compat.h" #include #include #include diff --git a/src/ps3/main.c b/src/ps3/main.c index 4eabdb9a1..bb657ba39 100644 --- a/src/ps3/main.c +++ b/src/ps3/main.c @@ -3,7 +3,7 @@ #include "rsxutil.h" #include "vm.h" -#include +#include "stdio_compat.h" #include #include #include diff --git a/src/ps3/ps3_overlay.c b/src/ps3/ps3_overlay.c index 5eaa4cacd..fc1c37064 100644 --- a/src/ps3/ps3_overlay.c +++ b/src/ps3/ps3_overlay.c @@ -6,7 +6,7 @@ #include "stb_ds.h" #include -#include +#include "stdio_compat.h" #include #include diff --git a/src/ps3/ps3_textures.c b/src/ps3/ps3_textures.c index b05cbc474..0c33b8e00 100644 --- a/src/ps3/ps3_textures.c +++ b/src/ps3/ps3_textures.c @@ -1,6 +1,6 @@ #include "ps3_textures.h" -#include +#include "stdio_compat.h" #include #include diff --git a/src/renderer.h b/src/renderer.h index 77dac7828..83759888c 100644 --- a/src/renderer.h +++ b/src/renderer.h @@ -3,7 +3,7 @@ #include "common.h" #include -#include +#include "stdio_compat.h" #include "math_compat.h" #include "matrix_math.h" #include "data_win.h" diff --git a/src/runner.c b/src/runner.c index a147ced1d..171532f6f 100644 --- a/src/runner.c +++ b/src/runner.c @@ -8,7 +8,7 @@ #include "collision.h" #include -#include +#include "stdio_compat.h" #include #include #include "math_compat.h" diff --git a/src/runner_mouse.c b/src/runner_mouse.c index a18119239..973d177ab 100644 --- a/src/runner_mouse.c +++ b/src/runner_mouse.c @@ -2,7 +2,7 @@ #include "utils.h" #include #include -#include +#include "stdio_compat.h" #include #include diff --git a/src/rvalue.h b/src/rvalue.h index af2eb90ab..4ce0dcb39 100644 --- a/src/rvalue.h +++ b/src/rvalue.h @@ -2,7 +2,7 @@ #define _BS_RVALUE_H_ #include #include "common.h" -#include +#include "stdio_compat.h" #include #include diff --git a/src/stdio_compat.h b/src/stdio_compat.h new file mode 100644 index 000000000..71fe631a0 --- /dev/null +++ b/src/stdio_compat.h @@ -0,0 +1,11 @@ +#ifndef _BS_COMPAT_STDIO_H_ +#define _BS_COMPAT_STDIO_H_ + +#include_next + +#ifdef NO_SNPRINTF +#include "nanoprintf.h" +#define snprintf npf_vsnprintf +#endif + +#endif /* _BS_COMPAT_STDIO_H_ */ diff --git a/src/string_builder.c b/src/string_builder.c index 2b5728a1b..26a0c6ab8 100644 --- a/src/string_builder.c +++ b/src/string_builder.c @@ -1,7 +1,7 @@ #include "string_builder.h" #include "utils.h" -#include +#include "stdio_compat.h" #include #include #include diff --git a/src/utils.h b/src/utils.h index c527822ba..12407a106 100644 --- a/src/utils.h +++ b/src/utils.h @@ -3,7 +3,7 @@ #include "common.h" #include -#include +#include "stdio_compat.h" #include #include #include diff --git a/src/vm.c b/src/vm.c index c7085f574..72b3bcb32 100644 --- a/src/vm.c +++ b/src/vm.c @@ -8,7 +8,7 @@ #include "profiler.h" #include "string_builder.h" -#include +#include "stdio_compat.h" #include #include #include "math_compat.h" diff --git a/src/vm_builtins.c b/src/vm_builtins.c index 5269f920a..cd82bbc42 100644 --- a/src/vm_builtins.c +++ b/src/vm_builtins.c @@ -10,7 +10,7 @@ #include "matrix_math.h" #include "utils.h" -#include +#include "stdio_compat.h" #include #include #include "math_compat.h" diff --git a/src/web-meta/main.c b/src/web-meta/main.c index 8bf23e105..351a5125c 100644 --- a/src/web-meta/main.c +++ b/src/web-meta/main.c @@ -1,5 +1,5 @@ #include "common.h" -#include +#include "stdio_compat.h" #include #include #include "data_win.h" diff --git a/src/web/main.c b/src/web/main.c index dd3380e47..986092f97 100644 --- a/src/web/main.c +++ b/src/web/main.c @@ -1,4 +1,4 @@ -#include +#include "stdio_compat.h" #include #include #include From 58c6f957b530b12e1b6ebe045a76075c4490f4b2 Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 08:48:44 -0400 Subject: [PATCH 09/57] fix --- src/stdio_compat.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stdio_compat.h b/src/stdio_compat.h index 71fe631a0..63b04d3ab 100644 --- a/src/stdio_compat.h +++ b/src/stdio_compat.h @@ -1,7 +1,7 @@ #ifndef _BS_COMPAT_STDIO_H_ #define _BS_COMPAT_STDIO_H_ -#include_next +#include #ifdef NO_SNPRINTF #include "nanoprintf.h" From 81e6ebcafcb324617a5d12af114b87afdf47b4cd Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 08:50:22 -0400 Subject: [PATCH 10/57] fix --- src/stdio_compat.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stdio_compat.h b/src/stdio_compat.h index 63b04d3ab..0b0178752 100644 --- a/src/stdio_compat.h +++ b/src/stdio_compat.h @@ -4,7 +4,7 @@ #include #ifdef NO_SNPRINTF -#include "nanoprintf.h" +#include #define snprintf npf_vsnprintf #endif From 50efee59a2dafc34304bdebeb0e991469ad9eaaf Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 08:52:00 -0400 Subject: [PATCH 11/57] fix --- src/stdio_compat.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stdio_compat.h b/src/stdio_compat.h index 0b0178752..702c22678 100644 --- a/src/stdio_compat.h +++ b/src/stdio_compat.h @@ -5,7 +5,7 @@ #ifdef NO_SNPRINTF #include -#define snprintf npf_vsnprintf +#define snprintf npf_snprintf #endif #endif /* _BS_COMPAT_STDIO_H_ */ From 06a868d2f82a314c0cd1df7f89635dcf854aa251 Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 08:53:38 -0400 Subject: [PATCH 12/57] build clean --- build.cmd | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build.cmd b/build.cmd index 73a48351b..c31cc0fef 100644 --- a/build.cmd +++ b/build.cmd @@ -1,6 +1,11 @@ @echo off setlocal enabledelayedexpansion +if "%1"=="clean" ( + if exist build rmdir /s /q build + exit /b 0 +) + if "%CC%"=="" set CC=cl if "%CFLAGS%"=="" set CFLAGS=/O2 /DNDEBUG /nologo From ad9355f23f0f15f7612f7cf5d1eedd6d4cab634a Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 08:59:59 -0400 Subject: [PATCH 13/57] struct syntax --- src/collision.h | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/collision.h b/src/collision.h index eeec90806..0ee4e30f0 100644 --- a/src/collision.h +++ b/src/collision.h @@ -33,8 +33,12 @@ static inline Sprite* Collision_getSprite(DataWin* dataWin, Instance* inst) { // Computes the axis-aligned bounding box for an instance using its collision sprite static inline InstanceBBox Collision_computeBBox(Runner* runner, Instance* inst) { + InstanceBBox ret; Sprite* spr = Collision_getSprite(runner->dataWin, inst); - if (spr == nullptr) return (InstanceBBox){0, 0, 0, 0, false}; + if (spr == nullptr) { + ZERO_STRUCT(ret); + return ret; + } GMLReal marginL = (spr->bboxMode == 1) ? 0.0 : (GMLReal) spr->marginLeft; GMLReal marginR = (spr->bboxMode == 1) ? (GMLReal) spr->width : (GMLReal) (spr->marginRight + 1); @@ -94,7 +98,12 @@ static inline InstanceBBox Collision_computeBBox(Runner* runner, Instance* inst) bottom = GMLReal_bankersRound(bottom); } - return (InstanceBBox){left, right, top, bottom, true}; + ret.left = left; + ret.right = right; + ret.top = top; + ret.buttom = bottom; + ret.valid = true; + return ret; } static inline bool Collision_hasFrameMasks(Sprite* sprite) { From d8ce68f175ed646ab4f04a4f5fc9f741c0b81f10 Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 09:00:18 -0400 Subject: [PATCH 14/57] fix --- src/collision.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/collision.h b/src/collision.h index 0ee4e30f0..034356ffb 100644 --- a/src/collision.h +++ b/src/collision.h @@ -101,7 +101,7 @@ static inline InstanceBBox Collision_computeBBox(Runner* runner, Instance* inst) ret.left = left; ret.right = right; ret.top = top; - ret.buttom = bottom; + ret.bottom = bottom; ret.valid = true; return ret; } From 8c4698d707ffb3245c483978f19945e8451068dc Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 09:02:24 -0400 Subject: [PATCH 15/57] fmin and fmax --- compat/configure.cmd | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/compat/configure.cmd b/compat/configure.cmd index ce35c6291..1ead977b2 100644 --- a/compat/configure.cmd +++ b/compat/configure.cmd @@ -32,6 +32,20 @@ if errorlevel 1 ( >>config.bat echo set SRCS=%%SRCS%% compat\stdio\nanoprintf_impl.c ) +>tmp\test.c echo.#include ^ +>>tmp\test.c echo.int main(void^){return fmin(0,0);} +call :check for fmin +if errorlevel 1 ( + >>config.bat echo set DEFINES=%%DEFINES%% /DNO_FMIN +) + +>tmp\test.c echo.#include ^ +>>tmp\test.c echo.int main(void^){return fmax(0,0);} +call :check for fmax +if errorlevel 1 ( + >>config.bat echo set DEFINES=%%DEFINES%% /DNO_FMAX +) + del tmp\test.c tmp\test.obj 2>nul exit /b 0 From e4ba28f05b0cd84dcb50622140d253d19d74656f Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 09:03:21 -0400 Subject: [PATCH 16/57] round --- compat/configure.cmd | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/compat/configure.cmd b/compat/configure.cmd index 1ead977b2..f8462749a 100644 --- a/compat/configure.cmd +++ b/compat/configure.cmd @@ -46,6 +46,13 @@ if errorlevel 1 ( >>config.bat echo set DEFINES=%%DEFINES%% /DNO_FMAX ) +>tmp\test.c echo.#include ^ +>>tmp\test.c echo.int main(void^){return round(0);} +call :check for round +if errorlevel 1 ( + >>config.bat echo set DEFINES=%%DEFINES%% /DNO_ROUND +) + del tmp\test.c tmp\test.obj 2>nul exit /b 0 From 74512813db7a3078276787d322d1d9700af7ff46 Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 09:06:05 -0400 Subject: [PATCH 17/57] maybe fix? --- src/instance.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/instance.c b/src/instance.c index 4be8162bb..1f409ab91 100644 --- a/src/instance.c +++ b/src/instance.c @@ -179,7 +179,7 @@ void Instance_computeSpeedFromComponents(Instance* inst) { if (GMLReal_fabs(inst->direction - GMLReal_round(inst->direction)) < 0.0001) { inst->direction = (float) GMLReal_round(inst->direction); } - inst->direction = (float) GMLReal_fmod(inst->direction, 360.0); + inst->direction = (float) GMLReal_fmod((GMLReal) inst->direction, 360.0); // Speed inst->speed = (float) GMLReal_sqrt(inst->hspeed * inst->hspeed + inst->vspeed * inst->vspeed); From 13e0f7a4114ffaa5b83989ce87894bed1d61fd5f Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 09:06:55 -0400 Subject: [PATCH 18/57] fix cast --- src/overlay_file_system.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/overlay_file_system.c b/src/overlay_file_system.c index 0fd3cd97d..ee2621d52 100644 --- a/src/overlay_file_system.c +++ b/src/overlay_file_system.c @@ -339,7 +339,7 @@ static void listSingleDir(FileSystemDirEntry** list, const char* fullDir) { #ifdef _WIN32 // FindFirstFileA wants a "/*" search pattern. size_t dirLen = strlen(fullDir); - char* search = safeMalloc(dirLen + 3); + char* search = (char *)safeMalloc(dirLen + 3); memcpy(search, fullDir, dirLen); search[dirLen] = '/'; search[dirLen + 1] = '*'; From cdee157f7ee04e036ebbdba3ba9e287463e6567e Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 09:08:33 -0400 Subject: [PATCH 19/57] msvc --- src/runner.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/runner.c b/src/runner.c index 171532f6f..ff718005a 100644 --- a/src/runner.c +++ b/src/runner.c @@ -1918,7 +1918,7 @@ static void cleanupState(Runner* runner) { free(file->content); free(file->writeBuffer); free(file->filePath); - *file = (OpenTextFile) {0}; + ZERO_STRUCT(*file); } } @@ -1928,7 +1928,7 @@ static void cleanupState(Runner* runner) { OpenBinaryFile* file = &runner->openBinaryFiles[i]; if (file->isOpen) { runner->fileSystem->vtable->binaryClose(runner->fileSystem, file->handle); - *file = (OpenBinaryFile) {0}; + ZERO_STRUCT(*file); } } From 6e875333f81bc91bb6830daab61cc5a6c1f20d2b Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 09:09:07 -0400 Subject: [PATCH 20/57] log2 --- compat/configure.cmd | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/compat/configure.cmd b/compat/configure.cmd index f8462749a..f65768e13 100644 --- a/compat/configure.cmd +++ b/compat/configure.cmd @@ -53,6 +53,13 @@ if errorlevel 1 ( >>config.bat echo set DEFINES=%%DEFINES%% /DNO_ROUND ) +>tmp\test.c echo.#include ^ +>>tmp\test.c echo.int main(void^){return log2(1);} +call :check for log2 +if errorlevel 1 ( + >>config.bat echo set DEFINES=%%DEFINES%% /DNO_LOG2 +) + del tmp\test.c tmp\test.obj 2>nul exit /b 0 From b6ff9c88b1e03e74a7b798a55ff861c12143488b Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 09:17:36 -0400 Subject: [PATCH 21/57] 1e39 --- src/math_compat.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/math_compat.h b/src/math_compat.h index fd7459f82..3aff983e8 100644 --- a/src/math_compat.h +++ b/src/math_compat.h @@ -107,7 +107,7 @@ static float roundf(float x) { #endif #ifndef INFINITY -#define INFINITY (1.0f / 0.0f) +#define INFINITY ((float)1e39) #endif #ifndef M_PI From ae92a35e3613b617512cfbcc007d62f83ed92fba Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 09:18:24 -0400 Subject: [PATCH 22/57] fix --- src/vm_builtins.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vm_builtins.c b/src/vm_builtins.c index cd82bbc42..62ba124c0 100644 --- a/src/vm_builtins.c +++ b/src/vm_builtins.c @@ -7167,7 +7167,7 @@ static RValue builtin_file_text_close(VMContext* ctx, RValue* args, int32_t argC free(file->content); free(file->writeBuffer); free(file->filePath); - *file = (OpenTextFile) {0}; + ZERO_STRUCT(*file); return RValue_makeUndefined(); } @@ -7465,7 +7465,7 @@ static RValue builtin_file_bin_close(VMContext* ctx, RValue* args, int32_t argCo OpenBinaryFile* file = getBinaryFile(runner, RValue_toInt32(args[0])); if (file == nullptr) return RValue_makeUndefined(); runner->fileSystem->vtable->binaryClose(runner->fileSystem, file->handle); - *file = (OpenBinaryFile) {0}; + ZERO_STRUCT(*file); return RValue_makeUndefined(); } From 809b3bc4af2a24f163703ab436e949a420b943b7 Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 09:20:50 -0400 Subject: [PATCH 23/57] msvc --- src/gl_legacy/gl_legacy_renderer.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/gl_legacy/gl_legacy_renderer.c b/src/gl_legacy/gl_legacy_renderer.c index b55b1ce8c..26ba2c531 100644 --- a/src/gl_legacy/gl_legacy_renderer.c +++ b/src/gl_legacy/gl_legacy_renderer.c @@ -1469,12 +1469,12 @@ static void glDeleteSprite(Renderer* renderer, int32_t spriteIndex) { static BlendFactors glGpuGetBlendFactors(Renderer* renderer) { GLLegacyRenderer* gl = (GLLegacyRenderer*)renderer; - return (BlendFactors){ - gl->currentSFactor, - gl->currentDFactor, - gl->currentSFactorAlpha, - gl->currentDFactorAlpha - }; + BlendFactors ret; + ret.src = gl->currentSFactor; + ret.dst = gl->currentDFactor; + ret.srcAlpha = gl->currentSFactorAlpha; + ret.dstAlpha = gl->currentDFactorAlpha; + return ret; } static int32_t glGpuGetBlendMode(Renderer* renderer) { From eb822d9965a207a3ccdfb4e24a85dfae44aea1e1 Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 09:22:04 -0400 Subject: [PATCH 24/57] msvc --- src/gl/gl_renderer.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/gl/gl_renderer.c b/src/gl/gl_renderer.c index 49bc43a92..48b0b4480 100644 --- a/src/gl/gl_renderer.c +++ b/src/gl/gl_renderer.c @@ -2337,12 +2337,12 @@ static void glDeleteSprite(Renderer* renderer, int32_t spriteIndex) { static BlendFactors glGpuGetBlendFactors(Renderer* renderer) { GLRenderer* gl = (GLRenderer*)renderer; - return (BlendFactors){ - gl->currentSFactor, - gl->currentDFactor, - gl->currentSFactorAlpha, - gl->currentDFactorAlpha - }; + BlendFactors ret; + ret.src = gl->currentSFactor; + ret.dst = gl->currentDFactor; + ret.srcAlpha = gl->currentSFactorAlpha; + ret.dstAlpha = gl->currentDFactorAlpha; + return ret; } static int32_t glGpuGetBlendMode(Renderer* renderer) { From dd0651e49c49c70b5d0617a5e9ed22b03fa3026c Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 09:27:46 -0400 Subject: [PATCH 25/57] fix --- build.cmd | 4 +++- compat/configure.cmd | 11 +++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/build.cmd b/build.cmd index c31cc0fef..ceef69d5c 100644 --- a/build.cmd +++ b/build.cmd @@ -120,10 +120,12 @@ if "%AUDIO_BACKEND%"=="openal" set SRCS=%SRCS% src\audio\openal\*.c if exist compat\config.bat call compat\config.bat +if not defined CC_COMPILE set CC_COMPILE=%CC% + if not exist build mkdir build for %%f in (%SRCS%) do ( - %CC% %CFLAGS% %DEFINES% %INCLUDES% /c "%%f" /Fobuild\ + %CC_COMPILE% %CFLAGS% %DEFINES% %INCLUDES% /c "%%f" /Fobuild\ if errorlevel 1 exit /b 1 ) diff --git a/compat/configure.cmd b/compat/configure.cmd index f65768e13..3a3cdac1e 100644 --- a/compat/configure.cmd +++ b/compat/configure.cmd @@ -60,6 +60,17 @@ if errorlevel 1 ( >>config.bat echo set DEFINES=%%DEFINES%% /DNO_LOG2 ) +>tmp\test.c echo.int main(void^){ +>>tmp\test.c echo. int a = 1; +>>tmp\test.c echo. ++a; +>>tmp\test.c echo. int b = a; +>>tmp\test.c echo. return b; +>>tmp\test.c echo.} +call :check if mixed declarations and code are supported +if errorlevel 1 ( + >>config.bat echo set CC_COMPILE=%%CC%% /TP +) + del tmp\test.c tmp\test.obj 2>nul exit /b 0 From 8fe16f3c0e9f07895bdbaecb24613ee7a20666e6 Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 09:30:56 -0400 Subject: [PATCH 26/57] update version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ef4d7e23c..a4f0bc55f 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ The following compilers have been tested to successfully build butterscotch, old * GCC 2.7 and up in C++ mode, and 3.0 and up in C99 mode * Clang 1.1 and up * TinyCC 0.9.27 and up -* MSVC 19.29.30159 and up +* MSVC 10.0 and up ## Community Ports From 02b514511a9d2833fa98bf95953f0bb87388be61 Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 10:08:17 -0400 Subject: [PATCH 27/57] __func__ --- compat/configure.cmd | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/compat/configure.cmd b/compat/configure.cmd index 3a3cdac1e..8b9aa0333 100644 --- a/compat/configure.cmd +++ b/compat/configure.cmd @@ -60,6 +60,16 @@ if errorlevel 1 ( >>config.bat echo set DEFINES=%%DEFINES%% /DNO_LOG2 ) +>tmp\test.c echo.#include ^ +>>tmp\test.c echo.int main(void^){ +>>tmp\test.c echo. puts(__func__); +>>tmp\test.c echo. return 0; +>>tmp\test.c echo.} +call :check if __func__ works +if errorlevel 1 ( + >>config.bat echo set DEFINES=%%DEFINES%% /D__func__=\"unknown\" +) + >tmp\test.c echo.int main(void^){ >>tmp\test.c echo. int a = 1; >>tmp\test.c echo. ++a; From 169050cb227641506f34c73a1cc49e7ceaef1f0d Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 10:09:53 -0400 Subject: [PATCH 28/57] roundf --- compat/configure.cmd | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/compat/configure.cmd b/compat/configure.cmd index 8b9aa0333..885b954cc 100644 --- a/compat/configure.cmd +++ b/compat/configure.cmd @@ -53,6 +53,13 @@ if errorlevel 1 ( >>config.bat echo set DEFINES=%%DEFINES%% /DNO_ROUND ) +>tmp\test.c echo.#include ^ +>>tmp\test.c echo.int main(void^){return roundf(0);} +call :check for roundf +if errorlevel 1 ( + >>config.bat echo set DEFINES=%%DEFINES%% /DNO_ROUNDF +) + >tmp\test.c echo.#include ^ >>tmp\test.c echo.int main(void^){return log2(1);} call :check for log2 From ff8b79b9411ff205030ec351b8de6c64c2d7b517 Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 10:35:39 -0400 Subject: [PATCH 29/57] better configure --- compat/stdio/nanoprintf_impl.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/compat/stdio/nanoprintf_impl.c b/compat/stdio/nanoprintf_impl.c index 0903bcde9..6079a20ad 100644 --- a/compat/stdio/nanoprintf_impl.c +++ b/compat/stdio/nanoprintf_impl.c @@ -1,2 +1,11 @@ #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_LARGE_FORMAT_SPECIFIERS 1 +#define NANOPRINTF_USE_SMALL_FORMAT_SPECIFIERS 1 +#define NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS 0 +#define NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS 0 +#define NANOPRINTF_USE_ALT_FORM_FLAG 1 +#define NANOPRINTF_USE_FLOAT_SINGLE_PRECISION 0 #include "nanoprintf.h" From d5b9ba654cca096c4ece855acef564da2de3576e Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 11:13:26 -0400 Subject: [PATCH 30/57] stdint --- compat/configure.cmd | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/compat/configure.cmd b/compat/configure.cmd index 885b954cc..38702fd56 100644 --- a/compat/configure.cmd +++ b/compat/configure.cmd @@ -20,6 +20,13 @@ if errorlevel 1 ( >>config.bat echo set INCLUDES=%%INCLUDES%% /Icompat\stdbool ) +>tmp\test.c echo.#include ^ +>>tmp\test.c echo.int main(void^){return 0;} +call :check if stdint.h works +if errorlevel 1 ( + >>config.bat echo set INCLUDES=%%INCLUDES%% /Icompat\stdint +) + >tmp\test.c echo.#include ^ >>tmp\test.c echo.int main(void^){ >>tmp\test.c echo. char buf[64]; From d42ae86a69d7cbe405846785966b5ebf1b031607 Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 11:37:31 -0400 Subject: [PATCH 31/57] printf --- compat/configure.cmd | 2 +- compat/stdio/nanoprintf.h | 1695 -------------------------------- compat/stdio/nanoprintf_impl.c | 11 - compat/stdio/printf.c | 1663 +++++++++++++++++++++++++++++++ compat/stdio/printf.h | 234 +++++ src/stdio_compat.h | 4 +- 6 files changed, 1900 insertions(+), 1709 deletions(-) delete mode 100644 compat/stdio/nanoprintf.h delete mode 100644 compat/stdio/nanoprintf_impl.c create mode 100644 compat/stdio/printf.c create mode 100644 compat/stdio/printf.h diff --git a/compat/configure.cmd b/compat/configure.cmd index 38702fd56..e77f46bfa 100644 --- a/compat/configure.cmd +++ b/compat/configure.cmd @@ -36,7 +36,7 @@ call :check for snprintf if errorlevel 1 ( >>config.bat echo set DEFINES=%%DEFINES%% /DNO_SNPRINTF >>config.bat echo set INCLUDES=%%INCLUDES%% /Icompat\stdio - >>config.bat echo set SRCS=%%SRCS%% compat\stdio\nanoprintf_impl.c + >>config.bat echo set SRCS=%%SRCS%% compat\stdio\printf.c ) >tmp\test.c echo.#include ^ diff --git a/compat/stdio/nanoprintf.h b/compat/stdio/nanoprintf.h deleted file mode 100644 index 07e8d32e0..000000000 --- a/compat/stdio/nanoprintf.h +++ /dev/null @@ -1,1695 +0,0 @@ -/* nanoprintf v0.6.0: 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. */ - -#ifndef NPF_H_INCLUDED -#define NPF_H_INCLUDED - -#ifdef NANOPRINTF_CONFIG_FILE -#include NANOPRINTF_CONFIG_FILE -#endif - -#include -#include - -typedef void (*npf_putc)(int c, void *ctx); - -// 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 - -#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) - #define NPF_PRINTF_ATTR(FORMAT_INDEX, VARGS_INDEX) \ - __attribute__((format(printf, FORMAT_INDEX, VARGS_INDEX))) -#else - #define NPF_PRINTF_ATTR(FORMAT_INDEX, VARGS_INDEX) -#endif - -#if defined(NANOPRINTF_USE_FLOAT_SINGLE_PRECISION) && \ - (NANOPRINTF_USE_FLOAT_SINGLE_PRECISION == 1) -#define NPF_PRINTF_SP_ATTR NPF_PRINTF_ATTR(3, 0) -#define NPF_MAP_ARGS(...) NPF__MAP(NPF__WRAP, __VA_ARGS__) -typedef struct { float val; } npf_float_t; -#define npf_snprintf_ npf_snprintf_sp_ -#define npf_pprintf_ npf_pprintf_sp_ -#define npf_vsnprintf npf_vsnprintf_sp -#define npf_vpprintf npf_vpprintf_sp -#else -#define NPF_PRINTF_SP_ATTR NPF_PRINTF_ATTR(3, 4) -#define NPF_MAP_ARGS(...) __VA_ARGS__ -#endif - -#ifdef __cplusplus -#define NPF_RESTRICT -extern "C" { -#else -#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) -#define NPF_RESTRICT restrict -#else -#define NPF_RESTRICT -#endif -#endif - -NPF_VISIBILITY int npf_snprintf_(char * NPF_RESTRICT buffer, - size_t bufsz, - const char * NPF_RESTRICT format, ...) - NPF_PRINTF_SP_ATTR; - -NPF_VISIBILITY int npf_pprintf_(npf_putc pc, - void * NPF_RESTRICT pc_ctx, - char const * NPF_RESTRICT format, ...) - NPF_PRINTF_SP_ATTR; - -// Public API - -// The npf_ functions all return the number of bytes required to express the -// fully-formatted string, not including the null terminator character. -// The npf_ functions do not return negative values, since the lack of 'l' length -// modifier support makes encoding errors impossible. - -#define npf_snprintf(buf, sz, ...) npf_snprintf_((buf), (sz), NPF_MAP_ARGS(__VA_ARGS__)) -#define npf_pprintf(pc, ctx, ...) npf_pprintf_((pc), (ctx), NPF_MAP_ARGS(__VA_ARGS__)) - -NPF_VISIBILITY int npf_vsnprintf(char * NPF_RESTRICT buffer, - size_t bufsz, - char const * NPF_RESTRICT format, - va_list vlist) NPF_PRINTF_ATTR(3, 0); - -NPF_VISIBILITY int npf_vpprintf(npf_putc pc, - void * NPF_RESTRICT pc_ctx, - char const * NPF_RESTRICT format, - va_list vlist) NPF_PRINTF_ATTR(3, 0); - -#ifdef __cplusplus -} -#endif - -#endif // NPF_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 NPF_IMPLEMENTATION_INCLUDED -#define NPF_IMPLEMENTATION_INCLUDED - -#include -#include - -// 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_LARGE_FORMAT_SPECIFIERS) && \ - !defined(NANOPRINTF_USE_SMALL_FORMAT_SPECIFIERS) && \ - !defined(NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS) && \ - !defined(NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS) && \ - !defined(NANOPRINTF_USE_ALT_FORM_FLAG) && \ - !defined(NANOPRINTF_USE_FLOAT_SINGLE_PRECISION) - #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_LARGE_FORMAT_SPECIFIERS 0 - #define NANOPRINTF_USE_SMALL_FORMAT_SPECIFIERS 1 - #define NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS 0 - #define NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS 0 - #define NANOPRINTF_USE_ALT_FORM_FLAG 1 - #define NANOPRINTF_USE_FLOAT_SINGLE_PRECISION 0 -#endif - -// Single-precision mode defaults to off unless explicitly enabled. -#ifndef NANOPRINTF_USE_FLOAT_SINGLE_PRECISION - #define NANOPRINTF_USE_FLOAT_SINGLE_PRECISION 0 -#endif - -// Optional flag, defaults to 0 if not explicitly configured. -#ifndef NANOPRINTF_USE_FLOAT_HEX_FORMAT_SPECIFIER - #define NANOPRINTF_USE_FLOAT_HEX_FORMAT_SPECIFIER 0 -#endif - -// Optional flag, defaults to 0 if not explicitly configured. Extracts digits -// without integer division; smaller on cores without a hardware divider. -#ifndef NANOPRINTF_USE_DIVISION_FREE_CONVERSION - #define NANOPRINTF_USE_DIVISION_FREE_CONVERSION 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_LARGE_FORMAT_SPECIFIERS - #error NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS must be #defined to 0 or 1 -#endif -#ifndef NANOPRINTF_USE_SMALL_FORMAT_SPECIFIERS - #error NANOPRINTF_USE_SMALL_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 -#ifndef NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS - #error NANOPRINTF_USE_WRITEBACK_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 - -#if (NANOPRINTF_USE_FLOAT_HEX_FORMAT_SPECIFIER == 1) && \ - (NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 0) - #error Float format specifiers must be enabled if float hex support is enabled. -#endif - -#if (NANOPRINTF_USE_FLOAT_SINGLE_PRECISION == 1) && \ - (NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 0) - #error Single precision requires float format specifiers to be enabled. -#endif - -#if NANOPRINTF_USE_FLOAT_SINGLE_PRECISION == 1 - #if defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL - #error Single-precision float mode requires /Zc:preprocessor on MSVC. - #endif - #ifdef __cplusplus - #if __cplusplus < 201103L && !defined(_MSC_VER) - #error Single-precision float wrapping requires C++11 or later. - #endif - #else - #if !(defined(__GNUC__) || defined(__clang__)) - #if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 201112L) - #error Single-precision float wrapping requires C11 or later (or GCC/Clang). - #endif - #endif - #endif -#endif - -// The conversion buffer must fit at least UINT64_MAX in octal format with the leading '0'. -// When floats are enabled, a larger buffer is needed for values like FLT_MAX / DBL_MAX. -#ifndef NANOPRINTF_CONVERSION_BUFFER_SIZE - #if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1 - #define NANOPRINTF_CONVERSION_BUFFER_SIZE 64 - #else - #define NANOPRINTF_CONVERSION_BUFFER_SIZE 23 - #endif -#endif -#if NANOPRINTF_CONVERSION_BUFFER_SIZE < 23 - #error The size of the conversion buffer must be at least 23 bytes. -#endif - -// intmax_t / uintmax_t require stdint from c99 / c++11 -#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1 - #ifndef _MSC_VER - #ifdef __cplusplus - #if __cplusplus < 201103L - #error large format specifier support requires C++11 or later. - #endif - #else - #if __STDC_VERSION__ < 199409L - #error nanoprintf requires C99 or later. - #endif - #endif - #endif -#endif - -// Figure out if we can disable warnings with pragmas. -#ifdef __clang__ - #define NPF_CLANG 1 - #define NPF_GCC_PAST_4_6 0 -#else - #define NPF_CLANG 0 - #if defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 6))) - #define NPF_GCC_PAST_4_6 1 - #else - #define NPF_GCC_PAST_4_6 0 - #endif -#endif - -#if NPF_CLANG || NPF_GCC_PAST_4_6 - #define NPF_HAVE_GCC_WARNING_PRAGMAS 1 -#else - #define NPF_HAVE_GCC_WARNING_PRAGMAS 0 -#endif - -#if NPF_HAVE_GCC_WARNING_PRAGMAS - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wpragmas" - #pragma GCC diagnostic ignored "-Wfloat-equal" - #pragma GCC diagnostic ignored "-Wgnu-statement-expression-from-macro-expansion" - #pragma GCC diagnostic ignored "-Wimplicit-fallthrough" - #pragma GCC diagnostic ignored "-Wpadded" - #pragma GCC diagnostic ignored "-Wpedantic" - #pragma GCC diagnostic ignored "-Wunused-function" - - #ifdef __cplusplus - #pragma GCC diagnostic ignored "-Wold-style-cast" - #endif - - #if NPF_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 NPF_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)) - #define NPF_FORCE_INLINE inline __attribute__((always_inline)) - - #define NPF_MIN(X, Y) ({ \ - __typeof__(X) const x = (X); __typeof__(Y) const y = (Y); x <= y ? x : y; }) - #define NPF_MAX(X, Y) ({ \ - __typeof__(X) const x = (X); __typeof__(Y) const y = (Y); x >= y ? x : y; }) -#else - #if defined(_MSC_VER) - #define NPF_NOINLINE __declspec(noinline) - #define NPF_FORCE_INLINE inline __forceinline - #else - #define NPF_NOINLINE - #define NPF_FORCE_INLINE - #endif - - #define NPF_MIN(X, Y) ((X) <= (Y) ? (X) : (Y)) - #define NPF_MAX(X, Y) ((X) >= (Y) ? (X) : (Y)) -#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_LEN_MOD_NONE, -#if NANOPRINTF_USE_SMALL_FORMAT_SPECIFIERS == 1 - NPF_FMT_SPEC_LEN_MOD_SHORT, // 'h' - NPF_FMT_SPEC_LEN_MOD_CHAR, // 'hh' -#endif - NPF_FMT_SPEC_LEN_MOD_LONG, // 'l' - NPF_FMT_SPEC_LEN_MOD_LONG_DOUBLE, // 'L' -#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1 - NPF_FMT_SPEC_LEN_MOD_LARGE_LONG_LONG, // 'll' - NPF_FMT_SPEC_LEN_MOD_LARGE_INTMAX, // 'j' - NPF_FMT_SPEC_LEN_MOD_LARGE_SIZET, // 'z' - NPF_FMT_SPEC_LEN_MOD_LARGE_PTRDIFFT, // 't' -#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' - NPF_FMT_SPEC_CONV_UNSIGNED_INT, // 'u' - NPF_FMT_SPEC_CONV_POINTER, // 'p' -#if NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS == 1 - NPF_FMT_SPEC_CONV_WRITEBACK, // 'n' -#endif -#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1 - NPF_FMT_SPEC_CONV_FLOAT_DEC, // 'f', 'F' - NPF_FMT_SPEC_CONV_FLOAT_SCI, // 'e', 'E' - NPF_FMT_SPEC_CONV_FLOAT_SHORTEST, // 'g', 'G' -#if NANOPRINTF_USE_FLOAT_HEX_FORMAT_SPECIFIER == 1 - NPF_FMT_SPEC_CONV_FLOAT_HEX, // 'a', 'A' -#endif -#endif -}; - -// Assert range order/comparisons to enforce C standard ordering -#define NPF_CONV_ORDER_ASSERT(NAME, COND) \ - typedef char npf_assert_##NAME[(COND) ? 1 : -1] -NPF_CONV_ORDER_ASSERT(text_convs_before_numeric, - (NPF_FMT_SPEC_CONV_PERCENT < NPF_FMT_SPEC_CONV_SIGNED_INT) && - (NPF_FMT_SPEC_CONV_CHAR < NPF_FMT_SPEC_CONV_SIGNED_INT) && - (NPF_FMT_SPEC_CONV_STRING < NPF_FMT_SPEC_CONV_SIGNED_INT)); -#if NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS == 1 -NPF_CONV_ORDER_ASSERT(int_convs_contiguous, - NPF_FMT_SPEC_CONV_UNSIGNED_INT == NPF_FMT_SPEC_CONV_SIGNED_INT + 4); -#else -NPF_CONV_ORDER_ASSERT(int_convs_contiguous, - NPF_FMT_SPEC_CONV_UNSIGNED_INT == NPF_FMT_SPEC_CONV_SIGNED_INT + 3); -#endif -NPF_CONV_ORDER_ASSERT(pointer_after_int_convs, - NPF_FMT_SPEC_CONV_POINTER > NPF_FMT_SPEC_CONV_UNSIGNED_INT); -#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1 -#if NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS == 1 -NPF_CONV_ORDER_ASSERT(float_convs_last, - NPF_FMT_SPEC_CONV_FLOAT_DEC > NPF_FMT_SPEC_CONV_WRITEBACK); -#else -NPF_CONV_ORDER_ASSERT(float_convs_last, - NPF_FMT_SPEC_CONV_FLOAT_DEC > NPF_FMT_SPEC_CONV_POINTER); -#endif -#endif -#undef NPF_CONV_ORDER_ASSERT - -typedef struct npf_format_spec { -#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1 - int field_width; -#endif -#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1 - int prec; - uint8_t prec_opt; -#endif -#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1 - uint8_t field_width_opt; - char left_justified; // '-' - char leading_zero_pad; // '0' -#endif - char prepend; // ' ' or '+' -#if NANOPRINTF_USE_ALT_FORM_FLAG == 1 - char alt_form; // '#' -#endif - char case_adjust; // 'a' - 'A' , or 0 (must be non-negative to work) - uint8_t length_modifier; - uint8_t conv_spec; -} npf_format_spec_t; - -#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1 - typedef intmax_t npf_int_t; - typedef uintmax_t npf_uint_t; - #define NPF_UINT_IS_WIDE 1 // uintmax_t is at least 64 bits -#elif ULONG_MAX > UINTPTR_MAX - typedef long npf_int_t; - typedef unsigned long npf_uint_t; - #define NPF_UINT_IS_WIDE (ULONG_MAX > 0xFFFFFFFFu) -#else - typedef intptr_t npf_int_t; - typedef uintptr_t npf_uint_t; - #define NPF_UINT_IS_WIDE (UINTPTR_MAX > 0xFFFFFFFFu) -#endif - -typedef struct npf_bufputc_ctx { - char *dst; // moving cursor; advances on each write while len > 0. - size_t len; // remaining capacity; decrements on each successful write. -} npf_bufputc_ctx_t; - -#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1 - typedef char npf_size_is_ptrdiff[(sizeof(size_t) == sizeof(ptrdiff_t)) ? 1 : -1]; - typedef ptrdiff_t npf_ssize_t; - typedef size_t npf_uptrdiff_t; -#endif - -#ifdef _MSC_VER - #include -#endif - -// Returns a pointer one past the last consumed character on success, null on -// failure. A pointer return inlines into npf_vpprintf with smaller code than -// a length return (the caller continues from the returned cursor directly). -static char const *npf_parse_format_spec_end(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->prepend = 0; -#if NANOPRINTF_USE_ALT_FORM_FLAG == 1 - out_spec->alt_form = 0; -#endif - - for (;;) { // cur points at the leading '%' character - switch (*++cur) { // Optional flags; '\0' exits via default. -#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1 - case '-': out_spec->left_justified = '-'; continue; - case '0': out_spec->leading_zero_pad = 1; continue; -#endif - case '+': - case ' ': if (out_spec->prepend != '+') { out_spec->prepend = *cur; } continue; -#if NANOPRINTF_USE_ALT_FORM_FLAG == 1 - case '#': out_spec->alt_form = '#'; continue; -#endif - default: break; - } - break; - } - -#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1 - out_spec->field_width = 0; - out_spec->field_width_opt = NPF_FMT_SPEC_OPT_NONE; - if (*cur == '*') { - out_spec->field_width_opt = NPF_FMT_SPEC_OPT_STAR; - ++cur; - } else { - while ((unsigned)(*cur - '0') < 10u) { - 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 ((unsigned)(*cur - '0') < 10u) { - out_spec->prec = (out_spec->prec * 10) + (*cur++ - '0'); - } - } - } -#endif - - out_spec->length_modifier = NPF_FMT_SPEC_LEN_MOD_NONE; - switch (*cur++) { // Length modifier -#if NANOPRINTF_USE_SMALL_FORMAT_SPECIFIERS == 1 - case 'h': - out_spec->length_modifier = NPF_FMT_SPEC_LEN_MOD_SHORT; - if (*cur == 'h') { - out_spec->length_modifier = NPF_FMT_SPEC_LEN_MOD_CHAR; - ++cur; - } - break; -#endif - case 'l': - out_spec->length_modifier = NPF_FMT_SPEC_LEN_MOD_LONG; -#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1 - if (*cur == 'l') { - out_spec->length_modifier = NPF_FMT_SPEC_LEN_MOD_LARGE_LONG_LONG; - ++cur; - } -#endif - break; -#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1 - case 'L': out_spec->length_modifier = NPF_FMT_SPEC_LEN_MOD_LONG_DOUBLE; break; -#endif -#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1 - case 'j': out_spec->length_modifier = NPF_FMT_SPEC_LEN_MOD_LARGE_INTMAX; break; - case 'z': out_spec->length_modifier = NPF_FMT_SPEC_LEN_MOD_LARGE_SIZET; break; - case 't': out_spec->length_modifier = NPF_FMT_SPEC_LEN_MOD_LARGE_PTRDIFFT; break; -#endif - default: --cur; break; - } - - // Conversion specifier. Look up the conv_spec from a 24-byte table indexed by - // (lowercased letter - 'a'). '%' is handled out-of-line since it's the only - // non-letter conversion. case_adjust gets bit 5 of the original char. - char const c = *cur++; - uint_fast8_t cs; - if (c == '%') { - cs = NPF_FMT_SPEC_CONV_PERCENT; - } else { - static const uint8_t lookup[24] = { -#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1 && NANOPRINTF_USE_FLOAT_HEX_FORMAT_SPECIFIER == 1 - NPF_FMT_SPEC_CONV_FLOAT_HEX, // 'a' -#else - 0, // 'a' -#endif -#if NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS == 1 - NPF_FMT_SPEC_CONV_BINARY, // 'b' -#else - 0, // 'b' -#endif - NPF_FMT_SPEC_CONV_CHAR, // 'c' - NPF_FMT_SPEC_CONV_SIGNED_INT, // 'd' -#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1 - NPF_FMT_SPEC_CONV_FLOAT_SCI, // 'e' - NPF_FMT_SPEC_CONV_FLOAT_DEC, // 'f' - NPF_FMT_SPEC_CONV_FLOAT_SHORTEST, // 'g' -#else - 0, 0, 0, // 'e', 'f', 'g' -#endif - 0, // 'h' (length modifier) - NPF_FMT_SPEC_CONV_SIGNED_INT, // 'i' - 0, 0, 0, 0, // 'j', 'k', 'l', 'm' -#if NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS == 1 - NPF_FMT_SPEC_CONV_WRITEBACK, // 'n' -#else - 0, // 'n' -#endif - NPF_FMT_SPEC_CONV_OCTAL, // 'o' - NPF_FMT_SPEC_CONV_POINTER, // 'p' - 0, 0, // 'q', 'r' - NPF_FMT_SPEC_CONV_STRING, // 's' - 0, // 't' - NPF_FMT_SPEC_CONV_UNSIGNED_INT, // 'u' - 0, 0, // 'v', 'w' - NPF_FMT_SPEC_CONV_HEX_INT, // 'x' - }; - unsigned const idx = (unsigned)((c | 32) - 'a'); - if (idx >= sizeof(lookup) || !(cs = lookup[idx])) { return NULL; } - } - out_spec->conv_spec = (uint8_t)cs; - out_spec->case_adjust = (char)(c & 32); // 32 for lowercase, 0 for uppercase - - return cur; -} - -// Length-returning facade over npf_parse_format_spec_end; 0 means failure. -static NPF_FORCE_INLINE int npf_parse_format_spec(char const *format, - npf_format_spec_t *out_spec) { - char const *const end = npf_parse_format_spec_end(format, out_spec); - return end ? (int)(end - format) : 0; -} - -#if NANOPRINTF_USE_DIVISION_FREE_CONVERSION == 1 -// Calculate div by 10 justing a shift and mask to avoid using hw div operands -static NPF_NOINLINE uint32_t npf_div10(uint32_t n) { - uint32_t q = (n >> 1) + (n >> 2); - q += q >> 4; - q += q >> 8; - q += q >> 16; - q >>= 3; - return q + ((n - (q * 10u) + 6u) >> 4); -} -#endif - -static NPF_NOINLINE char *npf_utoa_rev_end( - npf_uint_t val, char *buf, uint_fast8_t base, char case_adj) { -#if (NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1) || \ - ((NANOPRINTF_USE_DIVISION_FREE_CONVERSION == 1) && NPF_UINT_IS_WIDE) - // Use shift and subtract here to avoid hw div operation - while (val > 0xFFFFFFFFu) { - npf_uint_t q = 0, r = 0; - for (int i = (int)(sizeof(val) * 8) - 1; i >= 0; --i) { - r = (r << 1) | ((val >> i) & 1); - if (r >= (npf_uint_t)base) { r -= base; q |= (npf_uint_t)1 << i; } - } - int_fast8_t const d = (int_fast8_t)r; - *buf++ = (char)(((d < 10) ? '0' : ('A' - 10 + case_adj)) + d); - val = q; - } - uint32_t v32 = (uint32_t)val; -#else - npf_uint_t v32 = val; -#endif - do { -#if NANOPRINTF_USE_DIVISION_FREE_CONVERSION == 1 - int_fast8_t d; - if (base == 10u) { - uint32_t const q = npf_div10((uint32_t)v32); - d = (int_fast8_t)((uint32_t)v32 - (q * 10u)); - v32 = q; - } else { // base 8 or 16: shift and mask - d = (int_fast8_t)(v32 & (base - 1u)); - v32 >>= (base + 16u) >> 3; // 8 -> 3, 16 -> 4 - } -#else - int_fast8_t const d = (int_fast8_t)(v32 % base); - v32 /= base; -#endif - *buf++ = (char)(((d < 10) ? '0' : ('A' - 10 + case_adj)) + d); - } while (v32); - return buf; -} - -// Length-returning facade over npf_utoa_rev_end. -static NPF_FORCE_INLINE int npf_utoa_rev( - npf_uint_t val, char *buf, uint_fast8_t base, char case_adj) { - return (int)(npf_utoa_rev_end(val, buf, base, case_adj) - buf); -} - -#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1 - -#include - -#if NANOPRINTF_USE_FLOAT_SINGLE_PRECISION == 1 - typedef float npf_real_t; - #define NPF_REAL_MANT_DIG FLT_MANT_DIG - #define NPF_REAL_MAX_EXP FLT_MAX_EXP -#else - typedef double npf_real_t; - #define NPF_REAL_MANT_DIG DBL_MANT_DIG - #define NPF_REAL_MAX_EXP DBL_MAX_EXP -#endif - -#if (NPF_REAL_MANT_DIG <= 11) && (NPF_REAL_MAX_EXP <= 16) - typedef uint_fast16_t npf_real_bin_t; - typedef int_fast8_t npf_ftoa_exp_t; -#elif (NPF_REAL_MANT_DIG <= 24) && (NPF_REAL_MAX_EXP <= 128) - typedef uint_fast32_t npf_real_bin_t; - typedef int_fast16_t npf_ftoa_exp_t; -#elif (NPF_REAL_MANT_DIG <= 53) && (NPF_REAL_MAX_EXP <= 1024) - typedef uint_fast64_t npf_real_bin_t; - typedef int_fast16_t npf_ftoa_exp_t; -#else - #error Unsupported width of the real 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_REAL_EXP_MASK = NPF_REAL_MAX_EXP * 2 - 1, - NPF_REAL_EXP_BIAS = NPF_REAL_MAX_EXP - 1, - NPF_REAL_MAN_BITS = NPF_REAL_MANT_DIG - 1, - NPF_REAL_BIN_BITS = sizeof(npf_real_bin_t) * CHAR_BIT, - NPF_REAL_SIGN_POS = sizeof(npf_real_t) * CHAR_BIT - 1, - NPF_FTOA_MAN_BITS = sizeof(npf_ftoa_man_t) * CHAR_BIT, - NPF_FTOA_SHIFT_BITS = - ((NPF_FTOA_MAN_BITS < NPF_REAL_MANT_DIG) ? NPF_FTOA_MAN_BITS : NPF_REAL_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 NPF_FORCE_INLINE npf_real_bin_t npf_real_to_int_rep(npf_real_t f) { - // Union-cast is UB pre-C11 and in all C++; the compiler optimizes the code below. - npf_real_bin_t bin = 0; - char const *src = (char const *)&f; - char *dst = (char *)&bin; - for (uint_fast8_t i = 0; i < sizeof(f); ++i) { dst[i] = src[i]; } - return bin; -} - -#if NANOPRINTF_USE_DIVISION_FREE_CONVERSION == 1 -// Variable shifts of 64-bit values call sw helpers on archs -// without 64-bit shifters. Perfer 1 bit shits to keep in 32 bit word spce. -static NPF_FORCE_INLINE npf_real_bin_t npf_bin_shr(npf_real_bin_t v, int_fast8_t s) { - if (sizeof(v) > sizeof(uint32_t)) { while (s--) { v >>= 1; } return v; } - return (npf_real_bin_t)(v >> s); -} -static NPF_FORCE_INLINE npf_real_bin_t npf_bin_shl(npf_real_bin_t v, int_fast8_t s) { - if (sizeof(v) > sizeof(uint32_t)) { while (s--) { v <<= 1; } return v; } - return (npf_real_bin_t)(v << s); -} - #define NPF_BIN_SHR(V, S) npf_bin_shr((V), (int_fast8_t)(S)) - #define NPF_BIN_SHL(V, S) npf_bin_shl((V), (int_fast8_t)(S)) -#else - #define NPF_BIN_SHR(V, S) ((npf_real_bin_t)((V) >> (S))) - #define NPF_BIN_SHL(V, S) ((npf_real_bin_t)((V) << (S))) -#endif - -static int npf_ftoa_rev(char *buf, npf_format_spec_t const *spec, npf_real_t f) { - // Packed reversed specials "NAN"/"FNI"(inf)/"RRE"(err), selected by offset. - static char const specials[] = "NAN\0FNI\0RRE"; - char const *ret = NULL; - npf_real_bin_t bin = npf_real_to_int_rep(f); - - // 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_REAL_MAN_BITS) & NPF_REAL_EXP_MASK); - - bin &= ((npf_real_bin_t)0x1 << NPF_REAL_MAN_BITS) - 1; - if (!((unsigned)(exp + 1) & NPF_REAL_EXP_MASK)) { // special value - ret = specials + (bin ? 0 : 4); - goto exit; - } - if (spec->prec > (NANOPRINTF_CONVERSION_BUFFER_SIZE - 2)) { goto exit; } - if (exp) { // normal number - bin |= (npf_real_bin_t)0x1 << NPF_REAL_MAN_BITS; - } else { // subnormal number - ++exp; - } - exp = (npf_ftoa_exp_t)(exp - NPF_REAL_EXP_BIAS); - - uint_fast8_t carry; carry = 0; - npf_ftoa_dec_t end, dec; dec = (npf_ftoa_dec_t)spec->prec; - if (dec -#if NANOPRINTF_USE_ALT_FORM_FLAG == 1 - || spec->alt_form -#endif - ) { - 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_REAL_MAN_BITS - shift_i); - if (shift_i) { - npf_real_bin_t const bin_i = NPF_BIN_SHR(bin, shift_i - 1); - carry = (uint_fast8_t)(bin_i & 0x1); - man_i = (npf_ftoa_man_t)(bin_i >> 1); - } else { - man_i = (npf_ftoa_man_t)bin; - } - - if (exp_i) { - exp = NPF_REAL_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_BITS - 1))) { - man_i = (npf_ftoa_man_t)((man_i << 1) | carry); carry = 0; - } else { - if (dec >= NANOPRINTF_CONVERSION_BUFFER_SIZE) { goto exit; } - buf[dec++] = '0'; -#if NANOPRINTF_USE_DIVISION_FREE_CONVERSION == 1 - if (sizeof(man_i) <= sizeof(uint32_t)) { // n/5 = 2*(n/10) + (n%10 >= 5) - uint32_t const q = npf_div10((uint32_t)man_i); - uint_fast8_t r = (uint_fast8_t)((uint32_t)man_i - (q * 10u)); - man_i = (npf_ftoa_man_t)(q * 2u); - if (r >= 5u) { r = (uint_fast8_t)(r - 5u); ++man_i; } - carry = (uint_fast8_t)((r + carry + 1u) >> 2); - } else -#endif - { - carry = (uint_fast8_t)(((uint_fast8_t)(man_i % 5) + carry + 1u) >> 2); - man_i /= 5; - } - } - } - } else { - man_i = 0; - } - end = dec; - - // Print the integer. A 32-bit man_i has at most 10 decimal digits, so one - // up-front capacity check replaces the per-digit check and npf_utoa_rev - // becomes the shared decimal emitter. (Conservative: a value needing fewer - // digits than the remaining capacity in the last 9 bytes now reports ERR.) - if ((sizeof(npf_ftoa_man_t) <= sizeof(uint32_t)) && - (sizeof(npf_uint_t) >= sizeof(uint32_t))) { - if (end > NANOPRINTF_CONVERSION_BUFFER_SIZE - 10) { goto exit; } - end = (npf_ftoa_dec_t)(npf_utoa_rev_end((npf_uint_t)man_i, buf + end, 10, 0) - buf); - } else { // man_i may be wider than npf_uint_t: emit in place - do { - 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_REAL_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_real_bin_t bin_f = - NPF_BIN_SHL(bin, (NPF_REAL_BIN_BITS - NPF_REAL_MAN_BITS) + shift_f); - - // This if-else statement can be completely optimized at compile time. - if (NPF_REAL_BIN_BITS > NPF_FTOA_MAN_BITS) { - man_f = (npf_ftoa_man_t)(bin_f >> ((unsigned)(NPF_REAL_BIN_BITS - - NPF_FTOA_MAN_BITS) % - NPF_REAL_BIN_BITS)); - carry = (uint_fast8_t)((bin_f >> ((unsigned)(NPF_REAL_BIN_BITS - - NPF_FTOA_MAN_BITS - 1) % - NPF_REAL_BIN_BITS)) & 0x1); - } else { - man_f = (npf_ftoa_man_t)((npf_ftoa_man_t)bin_f - << ((unsigned)(NPF_FTOA_MAN_BITS - - NPF_REAL_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_REAL_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'; } - char const c = buf[dec]; - if (c == '.') { continue; } - if (c == '9') { buf[dec] = '0'; } - else { buf[dec] = (char)(c + 1); carry = 0; } - } - - return (int)end; -exit: - if (!ret) { ret = specials + 8; } - uint_fast8_t i = 0; - do { buf[i] = (char)(ret[i] + spec->case_adjust); } while (ret[++i]); - return -(int)i; -} - -#if NANOPRINTF_USE_FLOAT_HEX_FORMAT_SPECIFIER == 1 - -// Hex float always operates on IEEE 754 binary64 (double). -// When not in single-precision mode, npf_real_* already handles double. -#if NANOPRINTF_USE_FLOAT_SINGLE_PRECISION == 1 -typedef uint_fast64_t npf_double_bin_t; -enum { - NPF_DOUBLE_EXP_MASK = 2047, - NPF_DOUBLE_EXP_BIAS = 1023, - NPF_DOUBLE_MAN_BITS = 52, -}; -static NPF_FORCE_INLINE npf_double_bin_t npf_double_to_int_rep(double f) { - npf_double_bin_t bin = 0; - char const *src = (char const *)&f; - char *dst = (char *)&bin; - for (uint_fast8_t i = 0; i < sizeof(f); ++i) { dst[i] = src[i]; } - return bin; -} -#else -typedef npf_real_bin_t npf_double_bin_t; -#define NPF_DOUBLE_EXP_MASK NPF_REAL_EXP_MASK -#define NPF_DOUBLE_EXP_BIAS NPF_REAL_EXP_BIAS -#define NPF_DOUBLE_MAN_BITS NPF_REAL_MAN_BITS -#define npf_double_to_int_rep(f) npf_real_to_int_rep(f) -#endif - -static NPF_NOINLINE int npf_atoa_rev( - char *buf, npf_format_spec_t const *spec, double f) { - npf_double_bin_t bin = npf_double_to_int_rep(f); - 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) { return 0; } // caller uses ftoa_rev - - if (exp) { - bin |= (npf_double_bin_t)0x1 << NPF_DOUBLE_MAN_BITS; - exp = (npf_ftoa_exp_t)(exp - NPF_DOUBLE_EXP_BIAS); - } else if (bin) { - exp = (npf_ftoa_exp_t)(1 - NPF_DOUBLE_EXP_BIAS); - } - - { int const n_frac_dig = (NPF_DOUBLE_MAN_BITS + 3) / 4; - int const prec = NPF_MIN(spec->prec, n_frac_dig); - int end, i; - - // Discard low nibbles and round (only constant shifts of 3 and 4) - { npf_double_bin_t carry = 0; - for (i = n_frac_dig - prec; i > 0; --i) { - carry = (bin >> 3) & 1; - bin >>= 4; - } - bin += carry; - } - - { npf_ftoa_exp_t const ae = (exp < 0) ? (npf_ftoa_exp_t)-exp : exp; - end = npf_utoa_rev((npf_uint_t)ae, buf, 10, 0); - buf[end++] = (exp < 0) ? '-' : '+'; - buf[end++] = (char)('P' + spec->case_adjust); - } - - for (i = 0; i < prec; ++i) { - int_fast8_t const d = (int_fast8_t)(bin & 0xF); - buf[end++] = (char)(((d < 10) ? '0' : ('A' - 10 + spec->case_adjust)) + d); - bin >>= 4; - } - - if (prec > 0 -#if NANOPRINTF_USE_ALT_FORM_FLAG == 1 - || spec->alt_form -#endif - ) { buf[end++] = '.'; } - - buf[end++] = (char)('0' + (int_fast8_t)(bin & 0xF)); - return end; - } -} - -#endif // NANOPRINTF_USE_FLOAT_HEX_FORMAT_SPECIFIER - -#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. - #ifdef _M_X64 - #define NPF_HAVE_BUILTIN_CLZ - #define NPF_CLZ _BitScanReverse64 - #elif NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 0 - #define NPF_HAVE_BUILTIN_CLZ - #define NPF_CLZ _BitScanReverse - #endif - #ifdef NPF_HAVE_BUILTIN_CLZ - unsigned long idx; - NPF_CLZ(&idx, u); - return (int)(idx + 1); - #endif -#elif NPF_CLANG || NPF_GCC_PAST_4_6 - #define NPF_HAVE_BUILTIN_CLZ - #if NPF_UINT_IS_WIDE - #define NPF_CLZ(X) ((sizeof(long long) * CHAR_BIT) - (size_t)__builtin_clzll(X)) - #else - #define NPF_CLZ(X) ((sizeof(long) * CHAR_BIT) - (size_t)__builtin_clzl(X)) - #endif - 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 - -static void npf_bufputc(int c, void *ctx) { - npf_bufputc_ctx_t *bpc = (npf_bufputc_ctx_t *)ctx; - // NULL dst -> count-only mode (size-query semantics). - if (bpc->dst && bpc->len) { --bpc->len; *bpc->dst++ = (char)c; } -} - -#define NPF_PUTC(VAL) do { pc((int)(VAL), pc_ctx); ++npf_n; } while (0) -#define NPF_PUT(VAL) do { pc((int)(VAL), pc_ctx); } while (0) - -#define NPF_EXTRACT(DST, MOD, CAST_TO, EXTRACT_AS) \ - case NPF_FMT_SPEC_LEN_MOD_##MOD: DST = (CAST_TO)va_arg(args, EXTRACT_AS); break - -// When sizeof(long) == sizeof(int) -// va_arg(*args, long) and va_arg(*args, int) read the same bits, so the LONG -// case can fall into the int-promotion default. -#if LONG_MAX == INT_MAX - #define NPF_LONG_IS_INT 1 -#else - #define NPF_LONG_IS_INT 0 -#endif - -int npf_vpprintf(npf_putc pc, void *pc_ctx, char const *format, va_list args) { - npf_format_spec_t fs; - char const *cur = format; - int npf_n = 0; - - while (*cur) { - char const *const fs_end = - (*cur != '%') ? 0 : npf_parse_format_spec_end(cur, &fs); - if (!fs_end) { NPF_PUTC(*cur++); continue; } - cur = fs_end; - - // 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 = va_arg(args, int); - 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 = va_arg(args, int); - if (fs.prec < 0) { fs.prec = 0; fs.prec_opt = NPF_FMT_SPEC_OPT_NONE; } - } -#endif - -#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1 - // Set default precision (we can do that only now that we have extracted the - // argument-provided precision (".*"), and know whether to ignore that or not. - if (fs.prec_opt == NPF_FMT_SPEC_OPT_NONE) { - if (fs.conv_spec == NPF_FMT_SPEC_CONV_POINTER) { - fs.prec = (sizeof(void *) * CHAR_BIT + 3) / 4; - } -#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1 - // float specs are last in the enum; a single range check identifies them. - else if (fs.conv_spec >= NPF_FMT_SPEC_CONV_FLOAT_DEC) { -#if NANOPRINTF_USE_FLOAT_HEX_FORMAT_SPECIFIER == 1 - fs.prec = (fs.conv_spec == NPF_FMT_SPEC_CONV_FLOAT_HEX) - ? (NPF_DOUBLE_MAN_BITS + 3) / 4 : 6; -#else - fs.prec = 6; -#endif - } -#endif - } -#endif - -#if (NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1) && \ - (NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1) - // For d i o u x X, the '0' flag must be ignored if a precision is provided. - // Those conversions are contiguous in the enum (except BINARY, b). - if ((fs.prec_opt != NPF_FMT_SPEC_OPT_NONE) && - (fs.conv_spec >= NPF_FMT_SPEC_CONV_SIGNED_INT) && - (fs.conv_spec <= NPF_FMT_SPEC_CONV_UNSIGNED_INT) -#if NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS == 1 - && (fs.conv_spec != NPF_FMT_SPEC_CONV_BINARY) -#endif - ) { fs.leading_zero_pad = 0; } -#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; - char 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 - uint_fast8_t zero = 0; -#endif -#endif - - // Extract and convert the argument to string, point cbuf at the text. - // Range checks for INT and FLOAT families avoid an 11-entry dispatch table. -#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1 - if (fs.conv_spec >= NPF_FMT_SPEC_CONV_FLOAT_DEC) { - npf_real_t val; -#if NANOPRINTF_USE_FLOAT_SINGLE_PRECISION == 1 - val = va_arg(args, npf_float_t).val; -#elif LDBL_MANT_DIG == DBL_MANT_DIG - // long double has the same representation as double - // no need to branch on the 'L' length modifier. - val = va_arg(args, double); -#else - if (fs.length_modifier == NPF_FMT_SPEC_LEN_MOD_LONG_DOUBLE) { - val = (npf_real_t)va_arg(args, long double); - } else { - val = va_arg(args, double); - } -#endif - - { npf_real_bin_t const b = npf_real_to_int_rep(val); - sign_c = (b >> NPF_REAL_SIGN_POS) ? '-' : fs.prepend; -#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1 - zero = !(b & ~((npf_real_bin_t)1 << NPF_REAL_SIGN_POS)); -#endif - } -#if NANOPRINTF_USE_FLOAT_HEX_FORMAT_SPECIFIER == 1 - if ((fs.conv_spec == NPF_FMT_SPEC_CONV_FLOAT_HEX) && - ((cbuf_len = npf_atoa_rev(cbuf, &fs, (double)val)) > 0)) { - need_0x = (char)('X' + fs.case_adjust); - } else -#endif - { cbuf_len = npf_ftoa_rev(cbuf, &fs, val); } - if (cbuf_len < 0) { // negative means text (not number), so ignore the '0' flag - cbuf_len = -cbuf_len; -#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1 - fs.leading_zero_pad = 0; -#endif - } - } else -#endif -#if NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS == 1 - if (fs.conv_spec == NPF_FMT_SPEC_CONV_WRITEBACK) { - switch (fs.length_modifier) { - case NPF_FMT_SPEC_LEN_MOD_NONE: *(va_arg(args, int *)) = npf_n; break; -#if NANOPRINTF_USE_SMALL_FORMAT_SPECIFIERS == 1 - case NPF_FMT_SPEC_LEN_MOD_SHORT: *(va_arg(args, short *)) = (short)npf_n; break; - case NPF_FMT_SPEC_LEN_MOD_CHAR: *(va_arg(args, signed char *)) = (signed char)npf_n; break; -#endif - case NPF_FMT_SPEC_LEN_MOD_LONG: *(va_arg(args, long *)) = (long)npf_n; break; -#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1 - case NPF_FMT_SPEC_LEN_MOD_LARGE_LONG_LONG: *(va_arg(args, long long *)) = (long long)npf_n; break; - case NPF_FMT_SPEC_LEN_MOD_LARGE_INTMAX: *(va_arg(args, intmax_t *)) = (intmax_t)npf_n; break; - case NPF_FMT_SPEC_LEN_MOD_LARGE_SIZET: *(va_arg(args, npf_ssize_t *)) = (npf_ssize_t)npf_n; break; - case NPF_FMT_SPEC_LEN_MOD_LARGE_PTRDIFFT: *(va_arg(args, ptrdiff_t *)) = (ptrdiff_t)npf_n; break; -#endif - default: break; - } - } else -#endif - if (fs.conv_spec >= NPF_FMT_SPEC_CONV_SIGNED_INT) { - npf_uint_t val; - uint_fast8_t base = 10u; - - if (fs.conv_spec == NPF_FMT_SPEC_CONV_SIGNED_INT) { - npf_int_t sval = 0; -#if !NPF_LONG_IS_INT || NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1 - switch (fs.length_modifier) { -#if !NPF_LONG_IS_INT - NPF_EXTRACT(sval, LONG, long, long); -#endif -#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1 - NPF_EXTRACT(sval, LARGE_LONG_LONG, long long, long long); - NPF_EXTRACT(sval, LARGE_INTMAX, intmax_t, intmax_t); - NPF_EXTRACT(sval, LARGE_SIZET, npf_ssize_t, npf_ssize_t); - NPF_EXTRACT(sval, LARGE_PTRDIFFT, ptrdiff_t, ptrdiff_t); -#endif - default: -#endif - { - int v = va_arg(args, int); -#if NANOPRINTF_USE_SMALL_FORMAT_SPECIFIERS == 1 - if (fs.length_modifier == NPF_FMT_SPEC_LEN_MOD_SHORT) { v = (short)v; } - else if (fs.length_modifier == NPF_FMT_SPEC_LEN_MOD_CHAR) { v = (signed char)v; } -#endif - sval = v; - } -#if !NPF_LONG_IS_INT || NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1 - break; - } -#endif - sign_c = (sval < 0) ? '-' : fs.prepend; - val = (npf_uint_t)sval; - if (sval < 0) { val = 0 - val; } - } else { - if (fs.conv_spec == NPF_FMT_SPEC_CONV_POINTER) { - val = (npf_uint_t)(uintptr_t)va_arg(args, void *); - base = 16u; - } else { -#if !NPF_LONG_IS_INT || NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1 - switch (fs.length_modifier) { -#if !NPF_LONG_IS_INT - NPF_EXTRACT(val, LONG, unsigned long, unsigned long); -#endif -#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1 - NPF_EXTRACT(val, LARGE_LONG_LONG, unsigned long long, unsigned long long); - NPF_EXTRACT(val, LARGE_INTMAX, uintmax_t, uintmax_t); - NPF_EXTRACT(val, LARGE_SIZET, size_t, size_t); - NPF_EXTRACT(val, LARGE_PTRDIFFT, npf_uptrdiff_t, npf_uptrdiff_t); -#endif - default: -#endif - { - unsigned v = va_arg(args, unsigned); -#if NANOPRINTF_USE_SMALL_FORMAT_SPECIFIERS == 1 - if (fs.length_modifier == NPF_FMT_SPEC_LEN_MOD_SHORT) { v = (unsigned short)v; } - else if (fs.length_modifier == NPF_FMT_SPEC_LEN_MOD_CHAR) { v = (unsigned char)v; } -#endif - val = v; - } -#if !NPF_LONG_IS_INT || NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1 - break; - } -#endif - if (fs.conv_spec == NPF_FMT_SPEC_CONV_OCTAL) { base = 8u; } - else if (fs.conv_spec == NPF_FMT_SPEC_CONV_HEX_INT) { base = 16u; } - } - } - -#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) { - // cbuf_len was initialized to 0; preserved here. -#if NANOPRINTF_USE_ALT_FORM_FLAG == 1 - if (base == 8u && fs.alt_form) { fs.prec = 1; } // octal '#' special -#endif - } 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 - { cbuf_len = npf_utoa_rev(val, cbuf, base, fs.case_adjust); } - -#if NANOPRINTF_USE_ALT_FORM_FLAG == 1 - if (val && fs.alt_form) { - if (base == 8u) { - cbuf[cbuf_len++] = '0'; - } else if (base == 16u) { - need_0x = (char)('X' + fs.case_adjust); - } -#if NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS == 1 - else if (fs.conv_spec == NPF_FMT_SPEC_CONV_BINARY) { - need_0x = (char)('B' + fs.case_adjust); - } -#endif - } -#endif - } - } else if (fs.conv_spec == NPF_FMT_SPEC_CONV_STRING) { - cbuf = va_arg(args, char *); -#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1 - for (char const *s = cbuf; - ((fs.prec_opt == NPF_FMT_SPEC_OPT_NONE) || (cbuf_len < fs.prec)) && cbuf && *s; - ++s, ++cbuf_len); -#else - for (char const *s = cbuf; cbuf && *s; ++s, ++cbuf_len); // strlen -#endif - } else { - // PERCENT or CHAR: produce a 1-char buffer. - *cbuf = (fs.conv_spec == NPF_FMT_SPEC_CONV_CHAR) ? (char)va_arg(args, int) : '%'; - cbuf_len = 1; - } - -#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1 - // Compute the field width pad character. '0' flag only with numeric types, - // '-' overrides '0', and a blank result (prec.0 with zero value) suppresses '0'. - // With no field width, field_pad clamps to 0 below, so pad_c is never used. - pad_c = ' '; - if (fs.leading_zero_pad && !fs.left_justified -#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1 - && !((fs.prec_opt != NPF_FMT_SPEC_OPT_NONE) && !fs.prec && zero) -#endif - ) { pad_c = '0'; } -#endif - - // Compute the number of bytes to truncate or '0'-pad. Skip for STRING - // (already handled by precision-limited length) and FLOAT (precision is - // after the decimal point; float specs are last in the enum). -#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1 - if ((fs.conv_spec != NPF_FMT_SPEC_CONV_STRING) -#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1 - && (fs.conv_spec < NPF_FMT_SPEC_CONV_FLOAT_DEC) -#endif - ) { prec_pad = NPF_MAX(0, fs.prec - cbuf_len); } -#endif - - // Total bytes this conversion emits; npf_n is bulk-updated at the end. - int spec_len = cbuf_len + !!sign_c + (need_0x ? 2 : 0) -#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1 - + prec_pad -#endif - ; - -#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1 - // Given the full converted length, how many pad bytes? - field_pad = fs.field_width - spec_len; - if (field_pad < 0) { field_pad = 0; } - spec_len += field_pad; - - // Right-justified padding: zero-pad goes AFTER sign/0x; space-pad goes BEFORE. -#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1 - // '0'-padding is contiguous with the leading precision zeros, so fold it into - // prec_pad; sign/0x is then emitted exactly once below. prec_pad stays 0 for - // STRING unless folded into, so the hoisted loop is a no-op there. - if (pad_c == '0') { - prec_pad += field_pad; - field_pad = 0; - } -#else - if (pad_c == '0') { - if (sign_c) { NPF_PUT(sign_c); sign_c = 0; } - if (need_0x) { NPF_PUT('0'); NPF_PUT(need_0x); need_0x = 0; } - } -#endif - if (!fs.left_justified) { - while (field_pad-- > 0) { NPF_PUT(pad_c); } - } -#endif - if (sign_c) { NPF_PUT(sign_c); } - if (need_0x) { NPF_PUT('0'); NPF_PUT(need_0x); } -#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1 - while (prec_pad-- > 0) { NPF_PUT('0'); } // leading zeros: precision and '0' pad -#endif - - // Write the converted payload. The STRING parse loop guarantees cbuf_len == 0 - // when cbuf is NULL, so the output loop can elide the `cbuf &&` check. - if (fs.conv_spec == NPF_FMT_SPEC_CONV_STRING) { - for (int i = 0; i < cbuf_len; ++i) { NPF_PUT(cbuf[i]); } - } else { -#if NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS == 1 - if (fs.conv_spec == NPF_FMT_SPEC_CONV_BINARY) { - while (cbuf_len) { NPF_PUT('0' + ((u.binval >> --cbuf_len) & 1)); } - } else -#endif - { while (cbuf_len-- > 0) { NPF_PUT(cbuf[cbuf_len]); } } // payload is reversed - } - -#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1 - // Apply left-justified field width. The right-justified loop above has - // already run field_pad below zero in the non-left-justified case, so - // this loop body only executes for left-justified specifiers. - while (field_pad-- > 0) { NPF_PUT(pad_c); } -#endif - // NPF_PUT emissions don't tally npf_n; add the conversion's total length in bulk. - npf_n += spec_len; - } - - return npf_n; -} - -#undef NPF_PUTC -#undef NPF_PUT -#undef NPF_EXTRACT -#undef NPF_LONG_IS_INT -#undef NPF_BIN_SHR -#undef NPF_BIN_SHL - -int npf_vsnprintf(char * NPF_RESTRICT buffer, - size_t bufsz, - char const * NPF_RESTRICT format, - va_list vlist) { - npf_bufputc_ctx_t bufputc_ctx = { buffer, bufsz }; - int const n = npf_vpprintf(npf_bufputc, &bufputc_ctx, format, vlist); - - if (buffer && bufsz) { - // npf_vpprintf never returns negative (no encoding errors possible). -#ifdef NANOPRINTF_SNPRINTF_SAFE_EMPTY_STRING_ON_OVERFLOW - buffer[(unsigned)n >= bufsz ? 0 : (unsigned)n] = '\0'; -#else - buffer[NPF_MIN((unsigned)n, bufsz - 1)] = '\0'; -#endif - } - - return n; -} - -int npf_pprintf_(npf_putc pc, - void * NPF_RESTRICT pc_ctx, - char const * NPF_RESTRICT format, - ...) { - va_list val; - va_start(val, format); - int const rv = npf_vpprintf(pc, pc_ctx, format, val); - va_end(val); - return rv; -} - -int npf_snprintf_(char * NPF_RESTRICT buffer, - size_t bufsz, - const char * NPF_RESTRICT format, - ...) { - va_list val; - va_start(val, format); - int const rv = npf_vsnprintf(buffer, bufsz, format, val); - va_end(val); - return rv; -} - -#if NPF_HAVE_GCC_WARNING_PRAGMAS - #pragma GCC diagnostic pop -#endif - -#ifdef _MSC_VER - #pragma warning(pop) -#endif - -#endif // NPF_IMPLEMENTATION_INCLUDED -#endif // NANOPRINTF_IMPLEMENTATION - -// Single-precision argument wrapping and MAP macro expansion machinery. -// The npf_snprintf / npf_pprintf / NPF_MAP_ARGS macros defined above reference -// these, but that's fine: macro bodies are only expanded at the point of -// invocation, not at the point of definition. - -#ifndef NPF_MAP_INCLUDED -#define NPF_MAP_INCLUDED - -#if defined(NANOPRINTF_USE_FLOAT_SINGLE_PRECISION) && \ - (NANOPRINTF_USE_FLOAT_SINGLE_PRECISION == 1) - -// NPF__WRAP: wrap float/double args into npf_float_t, pass other types through. -// C++ uses function overloading; C uses _Generic with function-pointer selection. -// _Generic picks a function, then (x) calls it. Non-selected branches are bare -// function names (always valid), sidestepping _Generic's type-check-all-branches -// behavior that would reject (float)(string_ptr) in non-selected branches. -#if defined(__cplusplus) - extern "C++" { - static inline npf_float_t npf__wrap_impl(float f) { - npf_float_t r; r.val = f; return r; - } - static inline npf_float_t npf__wrap_impl(double d) { - npf_float_t r; r.val = (float)d; return r; - } - template static inline T npf__wrap_impl(T v) { return v; } - } - #define NPF__WRAP(x) npf__wrap_impl(x) -#elif defined(__GNUC__) || defined(__clang__) - #define NPF__IS_REAL(x) \ - (__builtin_types_compatible_p(__typeof__(0 ? (x) : (x)), float) || \ - __builtin_types_compatible_p(__typeof__(0 ? (x) : (x)), double)) - #define NPF__WRAP(x) __builtin_choose_expr(NPF__IS_REAL(x), \ - ({ npf_float_t _npf_r; \ - _npf_r.val = (float)__builtin_choose_expr(NPF__IS_REAL(x), (x), 0); \ - _npf_r; }), \ - (x)) -#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) - static inline npf_float_t npf__wf(float f) { - npf_float_t r; r.val = f; return r; - } - static inline npf_float_t npf__wd(double d) { - npf_float_t r; r.val = (float)d; return r; - } - static inline npf_float_t npf__wld(long double d) { - npf_float_t r; r.val = (float)d; return r; - } - - static inline int npf__id_i(int v) { return v; } - static inline unsigned npf__id_u(unsigned v) { return v; } - static inline long npf__id_l(long v) { return v; } - static inline unsigned long npf__id_ul(unsigned long v) { return v; } - static inline long long npf__id_ll(long long v) { return v; } - static inline unsigned long long npf__id_ull(unsigned long long v) { return v; } - static inline char npf__id_c(char v) { return v; } - static inline signed char npf__id_sc(signed char v) { return v; } - static inline unsigned char npf__id_uc(unsigned char v) { return v; } - static inline short npf__id_s(short v) { return v; } - static inline unsigned short npf__id_us(unsigned short v) { return v; } - static inline char *npf__id_cp(char *v) { return v; } - static inline char const *npf__id_ccp(char const *v) { return v; } - static inline void *npf__id_vp(void *v) { return v; } - static inline void const *npf__id_cvp(void const *v) { return v; } - static inline int *npf__id_ip(int *v) { return v; } - static inline short *npf__id_sp(short *v) { return v; } - static inline long *npf__id_lp(long *v) { return v; } - static inline long long *npf__id_llp(long long *v) { return v; } - static inline signed char *npf__id_scp(signed char *v) { return v; } - - #define NPF__WRAP(x) _Generic((x), \ - float: npf__wf, \ - double: npf__wd, \ - long double: npf__wld, \ - int: npf__id_i, \ - unsigned: npf__id_u, \ - long: npf__id_l, \ - unsigned long: npf__id_ul, \ - long long: npf__id_ll, \ - unsigned long long: npf__id_ull, \ - char: npf__id_c, \ - signed char: npf__id_sc, \ - unsigned char: npf__id_uc, \ - short: npf__id_s, \ - unsigned short: npf__id_us, \ - char *: npf__id_cp, \ - char const *: npf__id_ccp, \ - void *: npf__id_vp, \ - void const *: npf__id_cvp, \ - int *: npf__id_ip, \ - short *: npf__id_sp, \ - long *: npf__id_lp, \ - long long *: npf__id_llp, \ - signed char *: npf__id_scp)(x) -#else - #error Single-precision float wrapping requires C11, GCC/Clang, or C++. -#endif - -// Argument counting (up to 64 variadic args) -#define NPF__NARG(...) NPF__NARG_(__VA_ARGS__, NPF__RSEQ()) -#define NPF__NARG_(...) NPF__ARG_N(__VA_ARGS__) -#define NPF__ARG_N( \ - _1, _2, _3, _4, _5, _6, _7, _8, _9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \ - _21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \ - _41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \ - _61,_62,_63,_64,N,...) N -#define NPF__RSEQ() \ - 64,63,62,61,60,59,58,57,56,55,54,53,52,51,50,49,48,47,46,45,44,43,42,41,40,39,38, \ - 37,36,35,34,33,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11, \ - 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 - -// Token pasting -#define NPF__CAT(a,b) NPF__CAT_(a,b) -#define NPF__CAT_(a,b) a##b - -// MAP: apply f to each argument -#define NPF__MAP(f,...) NPF__CAT(NPF__MAP_,NPF__NARG(__VA_ARGS__))(f,__VA_ARGS__) -#define NPF__MAP_1(f,a) f(a) -#define NPF__MAP_2(f,a,...) f(a), NPF__MAP_1(f,__VA_ARGS__) -#define NPF__MAP_3(f,a,...) f(a), NPF__MAP_2(f,__VA_ARGS__) -#define NPF__MAP_4(f,a,...) f(a), NPF__MAP_3(f,__VA_ARGS__) -#define NPF__MAP_5(f,a,...) f(a), NPF__MAP_4(f,__VA_ARGS__) -#define NPF__MAP_6(f,a,...) f(a), NPF__MAP_5(f,__VA_ARGS__) -#define NPF__MAP_7(f,a,...) f(a), NPF__MAP_6(f,__VA_ARGS__) -#define NPF__MAP_8(f,a,...) f(a), NPF__MAP_7(f,__VA_ARGS__) -#define NPF__MAP_9(f,a,...) f(a), NPF__MAP_8(f,__VA_ARGS__) -#define NPF__MAP_10(f,a,...) f(a), NPF__MAP_9(f,__VA_ARGS__) -#define NPF__MAP_11(f,a,...) f(a), NPF__MAP_10(f,__VA_ARGS__) -#define NPF__MAP_12(f,a,...) f(a), NPF__MAP_11(f,__VA_ARGS__) -#define NPF__MAP_13(f,a,...) f(a), NPF__MAP_12(f,__VA_ARGS__) -#define NPF__MAP_14(f,a,...) f(a), NPF__MAP_13(f,__VA_ARGS__) -#define NPF__MAP_15(f,a,...) f(a), NPF__MAP_14(f,__VA_ARGS__) -#define NPF__MAP_16(f,a,...) f(a), NPF__MAP_15(f,__VA_ARGS__) -#define NPF__MAP_17(f,a,...) f(a), NPF__MAP_16(f,__VA_ARGS__) -#define NPF__MAP_18(f,a,...) f(a), NPF__MAP_17(f,__VA_ARGS__) -#define NPF__MAP_19(f,a,...) f(a), NPF__MAP_18(f,__VA_ARGS__) -#define NPF__MAP_20(f,a,...) f(a), NPF__MAP_19(f,__VA_ARGS__) -#define NPF__MAP_21(f,a,...) f(a), NPF__MAP_20(f,__VA_ARGS__) -#define NPF__MAP_22(f,a,...) f(a), NPF__MAP_21(f,__VA_ARGS__) -#define NPF__MAP_23(f,a,...) f(a), NPF__MAP_22(f,__VA_ARGS__) -#define NPF__MAP_24(f,a,...) f(a), NPF__MAP_23(f,__VA_ARGS__) -#define NPF__MAP_25(f,a,...) f(a), NPF__MAP_24(f,__VA_ARGS__) -#define NPF__MAP_26(f,a,...) f(a), NPF__MAP_25(f,__VA_ARGS__) -#define NPF__MAP_27(f,a,...) f(a), NPF__MAP_26(f,__VA_ARGS__) -#define NPF__MAP_28(f,a,...) f(a), NPF__MAP_27(f,__VA_ARGS__) -#define NPF__MAP_29(f,a,...) f(a), NPF__MAP_28(f,__VA_ARGS__) -#define NPF__MAP_30(f,a,...) f(a), NPF__MAP_29(f,__VA_ARGS__) -#define NPF__MAP_31(f,a,...) f(a), NPF__MAP_30(f,__VA_ARGS__) -#define NPF__MAP_32(f,a,...) f(a), NPF__MAP_31(f,__VA_ARGS__) -#define NPF__MAP_33(f,a,...) f(a), NPF__MAP_32(f,__VA_ARGS__) -#define NPF__MAP_34(f,a,...) f(a), NPF__MAP_33(f,__VA_ARGS__) -#define NPF__MAP_35(f,a,...) f(a), NPF__MAP_34(f,__VA_ARGS__) -#define NPF__MAP_36(f,a,...) f(a), NPF__MAP_35(f,__VA_ARGS__) -#define NPF__MAP_37(f,a,...) f(a), NPF__MAP_36(f,__VA_ARGS__) -#define NPF__MAP_38(f,a,...) f(a), NPF__MAP_37(f,__VA_ARGS__) -#define NPF__MAP_39(f,a,...) f(a), NPF__MAP_38(f,__VA_ARGS__) -#define NPF__MAP_40(f,a,...) f(a), NPF__MAP_39(f,__VA_ARGS__) -#define NPF__MAP_41(f,a,...) f(a), NPF__MAP_40(f,__VA_ARGS__) -#define NPF__MAP_42(f,a,...) f(a), NPF__MAP_41(f,__VA_ARGS__) -#define NPF__MAP_43(f,a,...) f(a), NPF__MAP_42(f,__VA_ARGS__) -#define NPF__MAP_44(f,a,...) f(a), NPF__MAP_43(f,__VA_ARGS__) -#define NPF__MAP_45(f,a,...) f(a), NPF__MAP_44(f,__VA_ARGS__) -#define NPF__MAP_46(f,a,...) f(a), NPF__MAP_45(f,__VA_ARGS__) -#define NPF__MAP_47(f,a,...) f(a), NPF__MAP_46(f,__VA_ARGS__) -#define NPF__MAP_48(f,a,...) f(a), NPF__MAP_47(f,__VA_ARGS__) -#define NPF__MAP_49(f,a,...) f(a), NPF__MAP_48(f,__VA_ARGS__) -#define NPF__MAP_50(f,a,...) f(a), NPF__MAP_49(f,__VA_ARGS__) -#define NPF__MAP_51(f,a,...) f(a), NPF__MAP_50(f,__VA_ARGS__) -#define NPF__MAP_52(f,a,...) f(a), NPF__MAP_51(f,__VA_ARGS__) -#define NPF__MAP_53(f,a,...) f(a), NPF__MAP_52(f,__VA_ARGS__) -#define NPF__MAP_54(f,a,...) f(a), NPF__MAP_53(f,__VA_ARGS__) -#define NPF__MAP_55(f,a,...) f(a), NPF__MAP_54(f,__VA_ARGS__) -#define NPF__MAP_56(f,a,...) f(a), NPF__MAP_55(f,__VA_ARGS__) -#define NPF__MAP_57(f,a,...) f(a), NPF__MAP_56(f,__VA_ARGS__) -#define NPF__MAP_58(f,a,...) f(a), NPF__MAP_57(f,__VA_ARGS__) -#define NPF__MAP_59(f,a,...) f(a), NPF__MAP_58(f,__VA_ARGS__) -#define NPF__MAP_60(f,a,...) f(a), NPF__MAP_59(f,__VA_ARGS__) -#define NPF__MAP_61(f,a,...) f(a), NPF__MAP_60(f,__VA_ARGS__) -#define NPF__MAP_62(f,a,...) f(a), NPF__MAP_61(f,__VA_ARGS__) -#define NPF__MAP_63(f,a,...) f(a), NPF__MAP_62(f,__VA_ARGS__) -#define NPF__MAP_64(f,a,...) f(a), NPF__MAP_63(f,__VA_ARGS__) - -#endif // NANOPRINTF_USE_FLOAT_SINGLE_PRECISION - -#endif // NPF_MAP_INCLUDED - -/* - 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. -*/ diff --git a/compat/stdio/nanoprintf_impl.c b/compat/stdio/nanoprintf_impl.c deleted file mode 100644 index 6079a20ad..000000000 --- a/compat/stdio/nanoprintf_impl.c +++ /dev/null @@ -1,11 +0,0 @@ -#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_LARGE_FORMAT_SPECIFIERS 1 -#define NANOPRINTF_USE_SMALL_FORMAT_SPECIFIERS 1 -#define NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS 0 -#define NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS 0 -#define NANOPRINTF_USE_ALT_FORM_FLAG 1 -#define NANOPRINTF_USE_FLOAT_SINGLE_PRECISION 0 -#include "nanoprintf.h" diff --git a/compat/stdio/printf.c b/compat/stdio/printf.c new file mode 100644 index 000000000..824e4699d --- /dev/null +++ b/compat/stdio/printf.c @@ -0,0 +1,1663 @@ +/** + * @author (c) Eyal Rozenberg + * 2021-2024, Haifa, Palestine/Israel + * @author (c) Marco Paland (info@paland.com) + * 2014-2019, PALANDesign Hannover, Germany + * + * @note Others have made smaller contributions to this file: see the + * contributors page at https://github.com/eyalroz/printf/graphs/contributors + * or ask one of the authors. The original code for exponential specifiers was + * contributed by Martijn Jasperse . + * + * @brief Small stand-alone implementation of the printf family of functions + * (`(v)printf`, `(v)s(n)printf` etc., geared towards use on embedded systems with + * limited resources. + * + * @note the implementations are thread-safe; re-entrant; use no functions from + * the standard library; and do not dynamically allocate any memory. + * + * @license The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 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 OR COPYRIGHT HOLDERS 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. + */ + +/* + * Define this globally (e.g. gcc -DPRINTF_INCLUDE_CONFIG_H=1 ...) to include the + * printf_config.h header file + */ +#if PRINTF_INCLUDE_CONFIG_H +#include "printf_config.h" +#endif + +#include + +#ifdef __cplusplus +#include +#include +#else +#include +#include +#include +#endif /* __cplusplus */ + +#if !(defined(__cplusplus) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L)) +/* C90 */ +#if defined(_MSC_VER) +#define inline __inline +#else +#define inline __inline__ +#endif /* defined(_MSC_VER) */ +#endif /* !(defined(__cplusplus) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L)) */ + +#if PRINTF_ALIAS_STANDARD_FUNCTION_NAMES_HARD +# define printf_ printf +# define sprintf_ sprintf +# define vsprintf_ vsprintf +# define snprintf_ snprintf +# define vsnprintf_ vsnprintf +# define vprintf_ vprintf +#endif /* PRINTF_ALIAS_STANDARD_FUNCTION_NAMES_HARD */ + + +/* + * 'ntoa' conversion buffer size, this must be big enough to hold one converted + * numeric number including padded zeros (dynamically created on stack) + */ +#ifndef PRINTF_INTEGER_BUFFER_SIZE +#define PRINTF_INTEGER_BUFFER_SIZE 32 +#endif /* PRINTF_INTEGER_BUFFER_SIZE */ + +/* + * size of the fixed (on-stack) buffer for printing individual decimal numbers. + * this must be big enough to hold one converted floating-point value including + * padded zeros. + */ +#ifndef PRINTF_DECIMAL_BUFFER_SIZE +#define PRINTF_DECIMAL_BUFFER_SIZE 32 +#endif + +/* Support for the decimal notation floating point conversion specifiers (%f, %F) */ +#ifndef PRINTF_SUPPORT_DECIMAL_SPECIFIERS +#define PRINTF_SUPPORT_DECIMAL_SPECIFIERS 1 +#endif + +/* Support for the exponential notation floating point conversion specifiers (%e, %g, %E, %G) */ +#ifndef PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS +#define PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS 1 +#endif + +/* Support for the length write-back specifier (%n) */ +#ifndef PRINTF_SUPPORT_WRITEBACK_SPECIFIER +#define PRINTF_SUPPORT_WRITEBACK_SPECIFIER 1 +#endif + +/* Default precision for the floating point conversion specifiers (the C standard sets this at 6) */ +#ifndef PRINTF_DEFAULT_FLOAT_PRECISION +#define PRINTF_DEFAULT_FLOAT_PRECISION 6 +#endif + +/* Default choice of type to use for internal floating-point computations */ +#ifndef PRINTF_USE_DOUBLE_INTERNALLY +#define PRINTF_USE_DOUBLE_INTERNALLY 1 +#endif + +/* + * According to the C languages standard, printf() and related functions must be able to print any + * integral number in floating-point notation, regardless of length, when using the %f specifier - + * possibly hundreds of characters, potentially overflowing your buffers. In this implementation, + * all values beyond this threshold are switched to exponential notation. + */ +#ifndef PRINTF_MAX_INTEGRAL_DIGITS_FOR_DECIMAL +#define PRINTF_MAX_INTEGRAL_DIGITS_FOR_DECIMAL 9 +#endif + +/* + * Support for the long long integral types (with the ll, z and t length modifiers for specifiers + * %d,%i,%o,%x,%X,%u, and with the %p specifier). + */ +#ifndef PRINTF_SUPPORT_LONG_LONG +#define PRINTF_SUPPORT_LONG_LONG 1 +#endif + +/* + * The number of terms in a Taylor series expansion of log_10(x) to + * use for approximation - including the power-zero term (i.e. the + * value at the point of expansion). + */ +#ifndef PRINTF_LOG10_TAYLOR_TERMS +#define PRINTF_LOG10_TAYLOR_TERMS 4 +#endif + +#if PRINTF_LOG10_TAYLOR_TERMS <= 1 +#error "At least one non-constant Taylor expansion is necessary for the log10() calculation" +#endif + +/* + * Be extra-safe, and don't assume format specifiers are completed correctly + * before the format string end. + */ +#ifndef PRINTF_CHECK_FOR_NUL_IN_FORMAT_SPECIFIER +#define PRINTF_CHECK_FOR_NUL_IN_FORMAT_SPECIFIER 1 +#endif + +#define PRINTF_PREFER_DECIMAL false +#define PRINTF_PREFER_EXPONENTIAL true + +/*===========================================================================*/ + +/* The following will convert the number-of-digits into an exponential-notation literal */ +#define PRINTF_CONCATENATE(s1, s2) s1##s2 +#define PRINTF_EXPAND_THEN_CONCATENATE(s1, s2) PRINTF_CONCATENATE(s1, s2) +#define PRINTF_FLOAT_NOTATION_THRESHOLD ((floating_point_t) PRINTF_EXPAND_THEN_CONCATENATE(1e,PRINTF_MAX_INTEGRAL_DIGITS_FOR_DECIMAL)) + +/* internal flag definitions */ +#define FLAGS_ZEROPAD (1U << 0U) +#define FLAGS_LEFT (1U << 1U) +#define FLAGS_PLUS (1U << 2U) +#define FLAGS_SPACE (1U << 3U) +#define FLAGS_HASH (1U << 4U) +#define FLAGS_UPPERCASE (1U << 5U) +#define FLAGS_CHAR (1U << 6U) +#define FLAGS_SHORT (1U << 7U) +#define FLAGS_INT (1U << 8U) + /* Only used with PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS */ +#define FLAGS_LONG (1U << 9U) +#define FLAGS_LONG_LONG (1U << 10U) +#define FLAGS_PRECISION (1U << 11U) +#define FLAGS_ADAPT_EXP (1U << 12U) +#define FLAGS_POINTER (1U << 13U) + /* Note: Similar, but not identical, effect as FLAGS_HASH */ +#define FLAGS_SIGNED (1U << 14U) +#define FLAGS_LONG_DOUBLE (1U << 15U) + /* Only used with PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS */ + +#if PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS + +#if (CHAR_MAX == 127) +#define FLAGS_INT8 FLAGS_CHAR +#else +#define PRINTF_NO_8_BIT_TYPE +#endif + +#if (SHRT_MAX == 32767LL) +#define FLAGS_INT16 FLAGS_SHORT +#elif (INT_MAX == 32767LL) +#define FLAGS_INT16 FLAGS_INT +#elif (LONG_MAX == 32767LL) +#define FLAGS_INT16 FLAGS_LONG +#elif (LLONG_MAX == 32767LL) +#define FLAGS_INT16 FLAGS_LONG_LONG +#else +#define PRINTF_NO_16_BIT_TYPE +#endif + +#if (SHRT_MAX == 2147483647LL) +#define FLAGS_INT32 FLAGS_SHORT +#elif (INT_MAX == 2147483647LL) +#define FLAGS_INT32 FLAGS_INT +#elif (LONG_MAX == 2147483647LL) +#define FLAGS_INT32 FLAGS_LONG +#elif (LLONG_MAX == 2147483647LL) +#define FLAGS_INT32 FLAGS_LONG_LONG +#else +#define PRINTF_NO_32_BIT_TYPE +#endif + +#if (SHRT_MAX == 9223372036854775807LL) +#define FLAGS_INT64 FLAGS_SHORT +#elif (INT_MAX == 9223372036854775807LL) +#define FLAGS_INT64 FLAGS_INT +#elif (LONG_MAX == 9223372036854775807LL) +#define FLAGS_INT64 FLAGS_LONG +#elif (LLONG_MAX == 9223372036854775807LL) +#define FLAGS_INT64 FLAGS_LONG_LONG +#else +#define PRINTF_NO_64_BIT_TYPE +#endif + +#endif /* PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS */ + + +typedef unsigned int printf_flags_t; + +#define BASE_BINARY 2 +#define BASE_OCTAL 8 +#define BASE_DECIMAL 10 +#define BASE_HEX 16 + +typedef uint8_t numeric_base_t; + +#if PRINTF_SUPPORT_LONG_LONG +typedef unsigned long long printf_unsigned_value_t; +typedef long long printf_signed_value_t; +#else +typedef unsigned long printf_unsigned_value_t; +typedef long printf_signed_value_t; +#endif /* PRINTF_SUPPORT_LONG_LONG */ + +/* + * The printf()-family functions return an `int`; it is therefore + * unnecessary/inappropriate to use size_t - often larger than int + * in practice - for non-negative related values, such as widths, + * precisions, offsets into buffers used for printing and the sizes + * of these buffers. instead, we use: + */ +typedef unsigned int printf_size_t; +#define PRINTF_MAX_POSSIBLE_BUFFER_SIZE INT_MAX + /* + * If we were to nitpick, this would actually be INT_MAX + 1, + * since INT_MAX is the maximum return value, which excludes the + * trailing '\0'. + */ + +#define SIGN(_negative,_x) ( (_negative) ? -(_x) : (_x)) + +#if (PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS) +#include +#if FLT_RADIX != 2 +#error "Non-binary-radix floating-point types are unsupported." +#endif /* PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS */ + +/** + * This library supports taking float-point arguments up to and including + * long double's; but - it currently does _not_ support internal + * representation and manipulation of values as long doubles; the options + * are either single-precision `float` or double-precision `double`. + */ +#if PRINTF_USE_DOUBLE_INTERNALLY +typedef double floating_point_t; +#define FP_TYPE_MANT_DIG DBL_MANT_DIG +#else +typedef float floating_point_t; +#define FP_TYPE_MANT_DIG FLT_MANT_DIG +#endif + +#define NUM_DECIMAL_DIGITS_IN_FP_INTEGRAL_COMPONENT_T 18 + +#if FP_TYPE_MANT_DIG == 24 + +typedef uint32_t printf_fp_uint_t; +#define FP_TYPE_SIZE_IN_BITS 32 +#define FP_TYPE_EXPONENT_MASK 0xFFU +#define FP_TYPE_BASE_EXPONENT 127 +#define FP_TYPE_MAX FLT_MAX +#define FP_TYPE_MAX_10_EXP FLT_MAX_10_EXP +#define FP_TYPE_MAX_SUBNORMAL_EXPONENT_OF_10 -38 +#define FP_TYPE_MAX_SUBNORMAL_POWER_OF_10 1e-38f +#define PRINTF_MAX_PRECOMPUTED_POWER_OF_10 10 + +#elif FP_TYPE_MANT_DIG == 53 + +typedef uint64_t printf_fp_uint_t; +#define FP_TYPE_SIZE_IN_BITS 64 +#define FP_TYPE_EXPONENT_MASK 0x7FFU +#define FP_TYPE_BASE_EXPONENT 1023 +#define FP_TYPE_MAX DBL_MAX +#define FP_TYPE_MAX_10_EXP DBL_MAX_10_EXP +#define FP_TYPE_MAX_SUBNORMAL_EXPONENT_OF_10 -308 +#define FP_TYPE_MAX_SUBNORMAL_POWER_OF_10 1e-308 +#define PRINTF_MAX_PRECOMPUTED_POWER_OF_10 NUM_DECIMAL_DIGITS_IN_FP_INTEGRAL_COMPONENT_T - 1 + + +#else /* FP_TYPE_MANT_DIG is neither 24 nor 53 */ +#error "Unsupported floating point type configuration" +#endif /* FP_TYPE_MANT_DIG */ +#define FP_TYPE_STORED_MANTISSA_BITS (FP_TYPE_MANT_DIG - 1) + +typedef union { + printf_fp_uint_t U; + floating_point_t F; +} floating_point_with_bit_access; + +/* + * This is unnecessary in C99, since compound initializers can be used, + * but: + * 1. Some compilers are finicky about this; + * 2. Some people may want to convert this to C89; + * 3. If you try to use it as C++, only C++20 supports compound literals + */ +static inline floating_point_with_bit_access get_bit_access(floating_point_t x) +{ + floating_point_with_bit_access fpwba; + fpwba.F = x; + return fpwba; +} + +static inline int get_sign_bit(floating_point_t x) +{ + /* The sign is stored in the highest bit */ + return (int) (get_bit_access(x).U >> (FP_TYPE_SIZE_IN_BITS - 1)); +} + +static inline int get_exp2(floating_point_with_bit_access x) +{ + /* + * The exponent in an IEEE-754 floating-point number occupies a contiguous + * sequence of bits (e.g. 52..62 for 64-bit doubles), but with a non-trivial representation: An + * unsigned offset from some negative value (with the extremal offset values reserved for + * special use). + */ + return (int)((x.U >> FP_TYPE_STORED_MANTISSA_BITS ) & FP_TYPE_EXPONENT_MASK) - FP_TYPE_BASE_EXPONENT; +} +#define PRINTF_ABS(_x) SIGN( (_x) < 0, (_x) ) + +#endif /* (PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS) */ + +/* + * The treatment of negative values here may seem a bit peculiar; it is intended to avoid + * undefined behavior for the case of the minimum signed value of x's type : if we were to + * try and negate it when _signed_, that would not be defined. But negating _unsigned_ values + * is well-defined, so we take that route. The semantics are also as we would like. See: + * @ref https://stackoverflow.com/a/17313717/1593077 (but TODO: Refer to the C language + * standard instead). + */ +#define ABS_FOR_PRINTING(_x) ( ((_x) >= 0) ? (printf_unsigned_value_t)(_x) : -((printf_unsigned_value_t)(_x)) ) + +/* + * wrapper (used as buffer) for output function type + * + * One of the following must hold: + * 1. max_chars is 0 + * 2. buffer is non-null + * 3. function is non-null + * + * ... otherwise bad things will happen. + */ +typedef struct { + void (*function)(char c, void* extra_arg); + void* extra_function_arg; + char* buffer; + printf_size_t pos; + printf_size_t max_chars; +} output_gadget_t; + +/* + * Note: This function currently assumes it is not passed a '\0' c, + * or alternatively, that '\0' can be passed to the function in the output + * gadget. The former assumption holds within the printf library. It also + * assumes that the output gadget has been properly initialized. + */ +static inline void putchar_via_gadget(output_gadget_t* gadget, char c) +{ + printf_size_t write_pos = gadget->pos++; + /* + * We're _always_ increasing pos, so as to count how may characters + * _would_ have been written if not for the max_chars limitation + */ + if (write_pos >= gadget->max_chars) { + return; + } + if (gadget->function != NULL) { + /* No check for c == '\0' . */ + gadget->function(c, gadget->extra_function_arg); + } + else { + /* + * it must be the case that gadget->buffer != NULL , due to the constraint + * on output_gadget_t ; and note we're relying on write_pos being non-negative. + */ + gadget->buffer[write_pos] = c; + } +} + +/* Possibly-write the string-terminating '\0' character */ +static inline void append_termination_via_gadget(output_gadget_t* gadget) +{ + printf_size_t null_char_pos; + if (gadget->function != NULL || gadget->max_chars == 0) { + return; + } + if (gadget->buffer == NULL) { + return; + } + null_char_pos = gadget->pos < gadget->max_chars ? gadget->pos : gadget->max_chars - 1; + gadget->buffer[null_char_pos] = '\0'; +} + +/* + * We can't use putchar_ as is, since our output gadget + * only takes pointers to functions with an extra argument + */ +static inline void putchar_wrapper(char c, void* unused) +{ + (void) unused; + putchar_(c); +} + +static inline output_gadget_t discarding_gadget(void) +{ + output_gadget_t gadget; + gadget.function = NULL; + gadget.extra_function_arg = NULL; + gadget.buffer = NULL; + gadget.pos = 0; + gadget.max_chars = 0; + return gadget; +} + +static inline output_gadget_t buffer_gadget(char* buffer, size_t buffer_size) +{ + printf_size_t usable_buffer_size = (buffer_size > PRINTF_MAX_POSSIBLE_BUFFER_SIZE) ? + PRINTF_MAX_POSSIBLE_BUFFER_SIZE : (printf_size_t) buffer_size; + output_gadget_t result = discarding_gadget(); + if (buffer != NULL) { + result.buffer = buffer; + result.max_chars = usable_buffer_size; + } + return result; +} + +static inline output_gadget_t function_gadget(void (*function)(char, void*), void* extra_arg) +{ + output_gadget_t result = discarding_gadget(); + result.function = function; + result.extra_function_arg = extra_arg; + result.max_chars = PRINTF_MAX_POSSIBLE_BUFFER_SIZE; + return result; +} + +static inline output_gadget_t extern_putchar_gadget(void) +{ + return function_gadget(putchar_wrapper, NULL); +} + +/* + * internal secure strlen + * @return The length of the string (excluding the terminating 0) limited by 'maxsize' + * @note strlen uses size_t, but wes only use this function with printf_size_t + * variables - hence the signature. + */ +static inline printf_size_t strnlen_s_(const char* str, printf_size_t maxsize) +{ + const char* s; + for (s = str; *s && maxsize--; ++s); + return (printf_size_t)(s - str); +} + + +/* + * internal test if char is a digit (0-9) + * @return true if char is a digit + */ +static inline bool is_digit_(char ch) +{ + return (ch >= '0') && (ch <= '9'); +} + + +/* internal ASCII string to printf_size_t conversion */ +static printf_size_t atou_(const char** str) +{ + printf_size_t i = 0U; + while (is_digit_(**str)) { + i = i * 10U + (printf_size_t)(*((*str)++) - '0'); + } + return i; +} + + +/* output the specified string in reverse, applying space-padding if necessary */ +static void out_rev_(output_gadget_t* output, const char* buf, printf_size_t len, printf_size_t width, printf_flags_t flags) +{ + const printf_size_t start_pos = output->pos; + + /* pad spaces up to given width */ + if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) { + printf_size_t i; + for (i = len; i < width; i++) { + putchar_via_gadget(output, ' '); + } + } + + /* reverse string */ + while (len) { + putchar_via_gadget(output, buf[--len]); + } + + /* append pad spaces up to given width */ + if (flags & FLAGS_LEFT) { + while (output->pos - start_pos < width) { + putchar_via_gadget(output, ' '); + } + } +} + + +/* + * Invoked by print_integer after the actual number has been printed, performing necessary + * work on the number's prefix (as the number is initially printed in reverse order) + */ +static void print_integer_finalization(output_gadget_t* output, char* buf, printf_size_t len, bool negative, numeric_base_t base, printf_size_t precision, printf_size_t width, printf_flags_t flags) +{ + printf_size_t unpadded_len = len; + + /* pad with leading zeros */ + { + if (!(flags & FLAGS_LEFT)) { + if (width && (flags & FLAGS_ZEROPAD) && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { + width--; + } + while ((flags & FLAGS_ZEROPAD) && (len < width) && (len < PRINTF_INTEGER_BUFFER_SIZE)) { + buf[len++] = '0'; + } + } + + while ((len < precision) && (len < PRINTF_INTEGER_BUFFER_SIZE)) { + buf[len++] = '0'; + } + + if (base == BASE_OCTAL && (len > unpadded_len)) { + /* Since we've written some zeros, we've satisfied the alternative format leading space requirement */ + flags &= ~FLAGS_HASH; + } + } + + /* handle hash */ + if (flags & (FLAGS_HASH | FLAGS_POINTER)) { + if (!(flags & FLAGS_PRECISION) && len && ((len == precision) || (len == width))) { + /* + * Let's take back some padding digits to fit in what will eventually + * be the format-specific prefix + */ + if (unpadded_len < len) { + len--; /* This should suffice for BASE_OCTAL */ + } + if (len && (base == BASE_HEX || base == BASE_BINARY) && (unpadded_len < len)) { + len--; /* ... and an extra one for 0x or 0b */ + } + } + if ((base == BASE_HEX) && !(flags & FLAGS_UPPERCASE) && (len < PRINTF_INTEGER_BUFFER_SIZE)) { + buf[len++] = 'x'; + } + else if ((base == BASE_HEX) && (flags & FLAGS_UPPERCASE) && (len < PRINTF_INTEGER_BUFFER_SIZE)) { + buf[len++] = 'X'; + } + else if ((base == BASE_BINARY) && (len < PRINTF_INTEGER_BUFFER_SIZE)) { + buf[len++] = 'b'; + } + if (len < PRINTF_INTEGER_BUFFER_SIZE) { + buf[len++] = '0'; + } + } + + if (len < PRINTF_INTEGER_BUFFER_SIZE) { + if (negative) { + buf[len++] = '-'; + } + else if (flags & FLAGS_PLUS) { + buf[len++] = '+'; /* ignore the space if the '+' exists */ + } + else if (flags & FLAGS_SPACE) { + buf[len++] = ' '; + } + } + + out_rev_(output, buf, len, width, flags); +} + +/* An internal itoa-like function */ +static void print_integer(output_gadget_t* output, printf_unsigned_value_t value, bool negative, numeric_base_t base, printf_size_t precision, printf_size_t width, printf_flags_t flags) +{ + char buf[PRINTF_INTEGER_BUFFER_SIZE]; + printf_size_t len = 0U; + + if (!value) { + if ( !(flags & FLAGS_PRECISION) ) { + buf[len++] = '0'; + flags &= ~FLAGS_HASH; + /* + * We drop this flag this since either the alternative and regular modes of the specifier + * don't differ on 0 values, or (in the case of octal) we've already provided the special + * handling for this mode. + */ + } + else if (base == BASE_HEX) { + flags &= ~FLAGS_HASH; + /* + * We drop this flag this since either the alternative and regular modes of the specifier + * don't differ on 0 values + */ + } + } + else { + do { + const char digit = (char)(value % base); + buf[len++] = (char)(digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10); + value /= base; + } while (value && (len < PRINTF_INTEGER_BUFFER_SIZE)); + } + + print_integer_finalization(output, buf, len, negative, base, precision, width, flags); +} + +#if (PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS) + +typedef int_fast64_t fp_integral_component_t; + +/* + * Stores a fixed-precision representation of a floating-point number relative + * to a fixed precision (which cannot be determined by examining this structure) + */ +struct floating_point_components { + fp_integral_component_t integral; + fp_integral_component_t fractional; + /* + * ... truncation of the actual fractional part of the floating_point_t value, scaled + * by the precision value + */ + bool is_negative; +}; + +static const floating_point_t powers_of_10[PRINTF_MAX_PRECOMPUTED_POWER_OF_10 + 1] = { + 1e00, 1e01, 1e02, 1e03, 1e04, 1e05, 1e06, 1e07, 1e08, 1e09, 1e10 +#if PRINTF_MAX_PRECOMPUTED_POWER_OF_10 > 10 + , 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17 +#endif +}; + +/* + * Note: This value does not mean that all floating-point values printed with the + * library will be correct up to this precision; it is just an upper-bound for + * avoiding buffer overruns and such + */ +#define PRINTF_MAX_SUPPORTED_PRECISION (NUM_DECIMAL_DIGITS_IN_FP_INTEGRAL_COMPONENT_T - 1) + + +/* + * Break up a non-negative, finite, floating-point number into two integral + * parts of its decimal representation: The number up to the decimal point, + * and the number appearing after that point - whose number of digits + * corresponds to the precision value. Example: The components of 12.621 + * are 12 and 621 for precision 3, or 12 and 62 for precision 2. + */ +static struct floating_point_components get_components(floating_point_t number, printf_size_t precision) +{ + struct floating_point_components number_; + floating_point_t abs_number; + floating_point_t scaled_remainder; + floating_point_t remainder; + const floating_point_t one_half = (floating_point_t) 0.5; + number_.is_negative = get_sign_bit(number); + abs_number = SIGN(number_.is_negative, number); + number_.integral = (fp_integral_component_t) abs_number; + scaled_remainder = (abs_number - (floating_point_t) number_.integral) * powers_of_10[precision]; + number_.fractional = (fp_integral_component_t) scaled_remainder; /* for precision == 0U, this will be 0 */ + + remainder = scaled_remainder - (floating_point_t) number_.fractional; + + if ((remainder > one_half) || + /* Banker's rounding, i.e. round half to even: 1.5 -> 2, but 2.5 -> 2 */ + ((remainder == one_half) && (number_.fractional & 1U))) { + ++number_.fractional; + } + if ((floating_point_t) number_.fractional >= powers_of_10[precision]) { + number_.fractional = 0; + ++number_.integral; + } + + if (precision == 0U) { + remainder = abs_number - (floating_point_t) number_.integral; + if ((remainder == one_half) && (number_.integral & 1U)) { + /* + * Banker's rounding, i.e. round half to even: + * 1.5 -> 2, but 2.5 -> 2 + */ + ++number_.integral; + } + } + return number_; +} + +#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS +struct scaling_factor { + floating_point_t raw_factor; + bool multiply; /* if true, need to multiply by raw_factor; otherwise need to divide by it */ +}; + +static floating_point_t apply_scaling(floating_point_t num, struct scaling_factor normalization) +{ + return normalization.multiply ? num * normalization.raw_factor : num / normalization.raw_factor; +} + +static floating_point_t unapply_scaling(floating_point_t normalized, struct scaling_factor normalization) +{ +#ifdef __GNUC__ +/* accounting for a static analysis bug in GCC 6.x and earlier */ +#pragma GCC diagnostic push +#if !defined(__has_warning) +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#elif __has_warning("-Wmaybe-uninitialized") +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif +#endif + return normalization.multiply ? normalized / normalization.raw_factor : normalized * normalization.raw_factor; +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif +} + +static struct scaling_factor update_normalization(struct scaling_factor sf, floating_point_t extra_multiplicative_factor) +{ + struct scaling_factor result; + if (sf.multiply) { + result.multiply = true; + result.raw_factor = sf.raw_factor * extra_multiplicative_factor; + } + else { + int factor_exp2 = get_exp2(get_bit_access(sf.raw_factor)); + int extra_factor_exp2 = get_exp2(get_bit_access(extra_multiplicative_factor)); + + /* Divide the larger-exponent raw raw_factor by the smaller */ + if (PRINTF_ABS(factor_exp2) > PRINTF_ABS(extra_factor_exp2)) { + result.multiply = false; + result.raw_factor = sf.raw_factor / extra_multiplicative_factor; + } + else { + result.multiply = true; + result.raw_factor = extra_multiplicative_factor / sf.raw_factor; + } + } + return result; +} + +static struct floating_point_components get_normalized_components(bool negative, printf_size_t precision, floating_point_t non_normalized, struct scaling_factor normalization, int floored_exp10) +{ + struct floating_point_components components; + floating_point_t scaled; + bool close_to_representation_extremum; + floating_point_t remainder; + floating_point_t prec_power_of_10; + struct scaling_factor account_for_precision; + floating_point_t scaled_remainder; + const floating_point_t rounding_threshold = 0.5; + + components.is_negative = negative; + scaled = apply_scaling(non_normalized, normalization); + + close_to_representation_extremum = ( (-floored_exp10 + (int) precision) >= FP_TYPE_MAX_10_EXP - 1 ); + if (close_to_representation_extremum) { + /* + * We can't have a normalization factor which also accounts for the precision, i.e. moves + * some decimal digits into the mantissa, since it's unrepresentable, or nearly unrepresentable. + * So, we'll give up early on getting extra precision... + */ + return get_components(SIGN(negative, scaled), precision); + } + components.integral = (fp_integral_component_t) scaled; + remainder = non_normalized - unapply_scaling((floating_point_t) components.integral, normalization); + prec_power_of_10 = powers_of_10[precision]; + account_for_precision = update_normalization(normalization, prec_power_of_10); + scaled_remainder = apply_scaling(remainder, account_for_precision); + + components.fractional = (fp_integral_component_t) scaled_remainder; /* when precision == 0, the assigned value should be 0 */ + scaled_remainder -= (floating_point_t) components.fractional; /* when precision == 0, this will not change scaled_remainder */ + + components.fractional += (scaled_remainder >= rounding_threshold); + if (scaled_remainder == rounding_threshold) { + /* banker's rounding: Round towards the even number (making the mean error 0) */ + components.fractional &= ~((fp_integral_component_t) 0x1); + } + /* + * handle rollover, e.g. the case of 0.99 with precision 1 becoming (0,100), + * and must then be corrected into (1, 0). + * Note: for precision = 0, this will "translate" the rounding effect from + * the fractional part to the integral part where it should actually be + * felt (as prec_power_of_10 is 1) + */ + if ((floating_point_t) components.fractional >= prec_power_of_10) { + components.fractional = 0; + ++components.integral; + } + return components; +} +#endif /* PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS */ + +static void print_broken_up_decimal( + struct floating_point_components number_, output_gadget_t* output, printf_size_t precision, + printf_size_t width, printf_flags_t flags, char *buf, printf_size_t len) +{ + if (precision != 0U) { + /* do fractional part, as an unsigned number */ + + printf_size_t count = precision; + + /* %g/%G mandates we skip the trailing 0 digits... */ + if ((flags & FLAGS_ADAPT_EXP) && !(flags & FLAGS_HASH) && (number_.fractional > 0)) { + while(true) { + fp_integral_component_t digit = number_.fractional % 10; + if (digit != 0) { + break; + } + --count; + number_.fractional /= 10U; + + } + /* + * ... and even the decimal point if there are no + * non-zero fractional part digits (see below) + */ + } + + if (number_.fractional > 0 || !(flags & FLAGS_ADAPT_EXP) || (flags & FLAGS_HASH) ) { + while (len < PRINTF_DECIMAL_BUFFER_SIZE) { + --count; + buf[len++] = (char)('0' + number_.fractional % 10); + if (!(number_.fractional /= 10U)) { + break; + } + } + /* add extra 0s */ + while ((len < PRINTF_DECIMAL_BUFFER_SIZE) && (count > 0U)) { + buf[len++] = '0'; + --count; + } + if (len < PRINTF_DECIMAL_BUFFER_SIZE) { + buf[len++] = '.'; + } + } + } + else { + if ((flags & FLAGS_HASH) && (len < PRINTF_DECIMAL_BUFFER_SIZE)) { + buf[len++] = '.'; + } + } + + /* + * Write the integer part of the number (it comes after the fractional + * since the character order is reversed) + */ + while (len < PRINTF_DECIMAL_BUFFER_SIZE) { + buf[len++] = (char)('0' + (number_.integral % 10)); + if (!(number_.integral /= 10)) { + break; + } + } + + /* pad leading zeros */ + if (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) { + if (width && (number_.is_negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { + width--; + } + while ((len < width) && (len < PRINTF_DECIMAL_BUFFER_SIZE)) { + buf[len++] = '0'; + } + } + + if (len < PRINTF_DECIMAL_BUFFER_SIZE) { + if (number_.is_negative) { + buf[len++] = '-'; + } + else if (flags & FLAGS_PLUS) { + buf[len++] = '+'; /* ignore the space if the '+' exists */ + } + else if (flags & FLAGS_SPACE) { + buf[len++] = ' '; + } + } + + out_rev_(output, buf, len, width, flags); +} + +/* internal ftoa for fixed decimal floating point */ +static void print_decimal_number(output_gadget_t* output, floating_point_t number, printf_size_t precision, printf_size_t width, printf_flags_t flags, char* buf, printf_size_t len) +{ + struct floating_point_components value_ = get_components(number, precision); + print_broken_up_decimal(value_, output, precision, width, flags, buf, len); +} + +#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS + +/* + * A floor function - but one which only works for numbers whose + * floor value is representable by an int. + */ +static int bastardized_floor(floating_point_t x) +{ + int n; + if (x >= 0) { return (int) x; } + n = (int) x; + return ( ((floating_point_t) n) == x ) ? n : n-1; +} + +/* + * Computes the base-10 logarithm of the input number - which must be an actual + * positive number (not infinity or NaN, nor a sub-normal) + */ +static floating_point_t log10_of_positive(floating_point_t positive_number) +{ + /* + * The implementation follows David Gay (https://www.ampl.com/netlib/fp/dtoa.c). + * + * Since log_10 ( M * 2^x ) = log_10(M) + x , we can separate the components of + * our input number, and need only solve log_10(M) for M between 1 and 2 (as + * the base-2 mantissa is always 1-point-something). In that limited range, a + * Taylor series expansion of log10(x) should serve us well enough; and we'll + * take the mid-point, 1.5, as the point of expansion. + */ + + floating_point_with_bit_access fpwba = get_bit_access(positive_number); + /* based on the algorithm by David Gay (https://www.ampl.com/netlib/fp/dtoa.c) */ + int exp2 = get_exp2(fpwba); + floating_point_t z; + /* drop the exponent, so fpwba.F comes into the range [1,2) */ + fpwba.U = (fpwba.U & (((printf_fp_uint_t) (1) << FP_TYPE_STORED_MANTISSA_BITS) - 1U)) | + ((printf_fp_uint_t) FP_TYPE_BASE_EXPONENT << FP_TYPE_STORED_MANTISSA_BITS); + z = (fpwba.F - (floating_point_t) 1.5); + return ( + /* Taylor expansion around 1.5: */ + (floating_point_t) 0.1760912590556812420 /* Expansion term 0: ln(1.5) / ln(10) */ + + z * (floating_point_t) 0.2895296546021678851 /* Expansion term 1: (M - 1.5) * 2/3 / ln(10) */ +#if PRINTF_LOG10_TAYLOR_TERMS > 2 + - z*z * (floating_point_t) 0.0965098848673892950 /* Expansion term 2: (M - 1.5)^2 * 2/9 / ln(10) */ +#if PRINTF_LOG10_TAYLOR_TERMS > 3 + + z*z*z * (floating_point_t) 0.0428932821632841311 /* Expansion term 2: (M - 1.5)^3 * 8/81 / ln(10) */ +#endif +#endif + /* exact log_2 of the exponent x, with logarithm base change */ + + (floating_point_t) exp2 * (floating_point_t) 0.30102999566398119521 /* = exp2 * log_10(2) = exp2 * ln(2)/ln(10) */ + ); +} + + +static floating_point_t pow10_of_int(int floored_exp10) +{ + floating_point_with_bit_access fpwba; + int exp2; + floating_point_t z; + floating_point_t z2; + + /* A crude hack for avoiding undesired behavior with barely-normal or slightly-subnormal values. */ + if (floored_exp10 == FP_TYPE_MAX_SUBNORMAL_EXPONENT_OF_10) { + return FP_TYPE_MAX_SUBNORMAL_POWER_OF_10; + } + /* Compute 10^(floored_exp10) but (try to) make sure that doesn't overflow */ + exp2 = bastardized_floor(floored_exp10 * (floating_point_t) 3.321928094887362 + (floating_point_t) 0.5); + z = floored_exp10 * (floating_point_t) 2.302585092994046 - exp2 * (floating_point_t) 0.6931471805599453; + z2 = z * z; + fpwba.U = ((printf_fp_uint_t)(exp2 + FP_TYPE_BASE_EXPONENT)) << FP_TYPE_STORED_MANTISSA_BITS; + /* + * compute exp(z) using continued fractions, + * see https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex + */ + fpwba.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14))))); + return fpwba.F; +} + +static void print_exponential_number(output_gadget_t* output, floating_point_t number, printf_size_t precision, printf_size_t width, printf_flags_t flags, char* buf, printf_size_t len) +{ + const bool negative = get_sign_bit(number); + /* This number will decrease gradually (by factors of 10) as we "extract" the exponent out of it */ + floating_point_t abs_number = SIGN(negative, number); + + int floored_exp10; + bool abs_exp10_covered_by_powers_table; + struct scaling_factor normalization; + struct floating_point_components decimal_part_components; + int original_floored_exp10; + bool fall_back_to_decimal_only_mode; + printf_size_t exp10_part_width; + printf_size_t decimal_part_width; + printf_size_t printed_exponential_start_pos; + + + /* Determine the decimal exponent */ + if (abs_number == (floating_point_t) 0.0) { + /* TODO: This is a special-case for 0.0 (and -0.0); but proper handling is required for denormals more generally. */ + floored_exp10 = 0; /* ... and no need to set a normalization factor or check the powers table */ + } + else { + floating_point_t exp10 = log10_of_positive(abs_number); + floating_point_t p10; + floored_exp10 = bastardized_floor(exp10); + p10 = pow10_of_int(floored_exp10); + /* correct for rounding errors */ + if (abs_number < p10) { + floored_exp10--; + p10 /= 10; + } + abs_exp10_covered_by_powers_table = PRINTF_ABS(floored_exp10) < PRINTF_MAX_PRECOMPUTED_POWER_OF_10; + normalization.raw_factor = abs_exp10_covered_by_powers_table ? powers_of_10[PRINTF_ABS(floored_exp10)] : p10; + } + + if (flags & FLAGS_ADAPT_EXP) { + /* + * Note: For now, still assuming we _don't_ fall-back to "%f" mode; we can't decide + * that until we've established the exact exponent. + */ + + /* + * In "%g" mode, "precision" is the number of _significant digits_; we must + * "translate" that to an actual number of decimal digits. + */ + precision = (precision > 1) ? (precision - 1U) : 0U; + flags |= FLAGS_PRECISION; /* make sure print_broken_up_decimal respects our choice */ + } + + /* + * We now begin accounting for the widths of the two parts of our printed field: + * the decimal part after decimal exponent extraction, and the base-10 exponent part. + * For both of these, the value of 0 has a special meaning, but not the same one: + * a 0 exponent-part width means "don't print the exponent"; a 0 decimal-part width + * means "use as many characters as necessary". + */ + +#ifdef __GNUC__ +/* accounting for a static analysis bug in GCC 6.x and earlier */ +#pragma GCC diagnostic push +#if !defined(__has_warning) +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#elif __has_warning("-Wmaybe-uninitialized") +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif +#endif + normalization.multiply = (floored_exp10 < 0 && abs_exp10_covered_by_powers_table); +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + decimal_part_components = + (floored_exp10 == 0) ? + get_components(SIGN(negative, abs_number), precision) : + get_normalized_components(negative, precision, abs_number, normalization, floored_exp10); + + /* + * Account for roll-over, e.g. rounding from 9.99 to 100.0 - which effects + * the exponent and may require additional tweaking of the parts + * (and saving the floored_exp10 in case we'll need to undo the roll-over). + */ + original_floored_exp10 = floored_exp10; + if (decimal_part_components.integral >= 10) { + floored_exp10++; + decimal_part_components.integral = 1; + decimal_part_components.fractional = 0; + } + + /* + * Should we want to fall-back to "%f" mode, and only print the decimal part? + * (and remember we have decreased "precision" by 1 + */ + fall_back_to_decimal_only_mode = (flags & FLAGS_ADAPT_EXP) && (floored_exp10 >= -4) && (floored_exp10 < (int) precision + 1); + + if (fall_back_to_decimal_only_mode) { + precision = ((int) precision > floored_exp10) ? (unsigned) ((int) precision - floored_exp10) : 0U; + /* Redo some work :-) */ + floored_exp10 = original_floored_exp10; + decimal_part_components = get_components(SIGN(negative, abs_number), precision); + if ((flags & FLAGS_ADAPT_EXP) && floored_exp10 >= -1 && decimal_part_components.integral == (fp_integral_component_t) powers_of_10[floored_exp10 + 1]) { + floored_exp10++; /* Not strictly necessary, since floored_exp10 is no longer really used */ + if (precision > 0U) { precision--; } + /* ... and it should already be the case that decimal_part_components.fractional == 0 */ + } + /* TODO: What about rollover strictly within the fractional part? */ + } + + /* + * the floored_exp10 format is "E%+03d" and largest possible floored_exp10 value for a 64-bit double + * is "307" (for 2^1023), so we set aside 4-5 characters overall + */ + exp10_part_width = fall_back_to_decimal_only_mode ? 0U : (PRINTF_ABS(floored_exp10) < 100) ? 4U : 5U; + + decimal_part_width = + ((flags & FLAGS_LEFT) && exp10_part_width) ? + /* + * We're padding on the right, so the width constraint is the exponent part's + * problem, not the decimal part's, so we'll use as many characters as we need: + */ + 0U : + /* + * We're padding on the left; so the width constraint is the decimal part's + * problem. Well, can both the decimal part and the exponent part fit within our overall width? + */ + ((width > exp10_part_width) ? + /* + * Yes, so we limit our decimal part's width. + * (Note this is trivially valid even if we've fallen back to "%f" mode) + */ + width - exp10_part_width : + /* + * No; we just give up on any restriction on the decimal part and use as many + * characters as we need + */ + 0U); + + printed_exponential_start_pos = output->pos; + print_broken_up_decimal(decimal_part_components, output, precision, decimal_part_width, flags, buf, len); + + if (! fall_back_to_decimal_only_mode) { + putchar_via_gadget(output, (flags & FLAGS_UPPERCASE) ? 'E' : 'e'); + print_integer(output, + ABS_FOR_PRINTING(floored_exp10), + floored_exp10 < 0, 10, 0, exp10_part_width - 1, + FLAGS_ZEROPAD | FLAGS_PLUS); + if (flags & FLAGS_LEFT) { + /* We need to right-pad with spaces to meet the width requirement */ + while (output->pos - printed_exponential_start_pos < width) { + putchar_via_gadget(output, ' '); + } + } + } +} +#endif /* PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS */ + +static void print_floating_point(output_gadget_t* output, floating_point_t value, printf_size_t precision, printf_size_t width, printf_flags_t flags, bool prefer_exponential) +{ + char buf[PRINTF_DECIMAL_BUFFER_SIZE]; + printf_size_t len = 0U; + + /* test for special values */ + if (value != value) { + out_rev_(output, "nan", 3, width, flags); + return; + } + if (value < -FP_TYPE_MAX) { + out_rev_(output, "fni-", 4, width, flags); + return; + } + if (value > FP_TYPE_MAX) { + out_rev_(output, (flags & FLAGS_PLUS) ? "fni+" : "fni", (flags & FLAGS_PLUS) ? 4U : 3U, width, flags); + return; + } + + if (!prefer_exponential && + ((value > PRINTF_FLOAT_NOTATION_THRESHOLD) || (value < -PRINTF_FLOAT_NOTATION_THRESHOLD))) { + /* + * The required behavior of standard printf is to print _every_ integral-part digit -- which could mean + * printing hundreds of characters, overflowing any fixed internal buffer and necessitating a more complicated + * implementation. + */ +#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS + print_exponential_number(output, value, precision, width, flags, buf, len); +#endif + return; + } + + /* set default precision, if not set explicitly */ + if (!(flags & FLAGS_PRECISION)) { + precision = PRINTF_DEFAULT_FLOAT_PRECISION; + } + + /* limit precision so that our integer holding the fractional part does not overflow */ + while ((len < PRINTF_DECIMAL_BUFFER_SIZE) && (precision > PRINTF_MAX_SUPPORTED_PRECISION)) { + buf[len++] = '0'; /* This respects the precision in terms of result length only */ + precision--; + } + +#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS + if (prefer_exponential) + print_exponential_number(output, value, precision, width, flags, buf, len); + else +#endif + print_decimal_number(output, value, precision, width, flags, buf, len); +} + +#endif /* (PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS) */ + +/* + * Advances the format pointer past the flags, and returns the parsed flags + * due to the characters passed + */ +static printf_flags_t parse_flags(const char** format) +{ + printf_flags_t flags = 0U; + do { + switch (**format) { + case '0': flags |= FLAGS_ZEROPAD; (*format)++; break; + case '-': flags |= FLAGS_LEFT; (*format)++; break; + case '+': flags |= FLAGS_PLUS; (*format)++; break; + case ' ': flags |= FLAGS_SPACE; (*format)++; break; + case '#': flags |= FLAGS_HASH; (*format)++; break; + default : return flags; + } + } while (true); +} + +static inline void format_string_loop(output_gadget_t* output, const char* format, va_list args) +{ +#if PRINTF_CHECK_FOR_NUL_IN_FORMAT_SPECIFIER +#define ADVANCE_IN_FORMAT_STRING(cptr_) do { (cptr_)++; if (!*(cptr_)) return; } while(0) +#else +#define ADVANCE_IN_FORMAT_STRING(cptr_) (cptr_)++ +#endif + + while (*format) + { + printf_flags_t flags; + printf_size_t width; + printf_size_t precision; + if (*format != '%') { + /* A regular content character */ + putchar_via_gadget(output, *format); + format++; + continue; + } + /* We're parsing a format specifier: %[flags][width][.precision][length] */ + ADVANCE_IN_FORMAT_STRING(format); + + flags = parse_flags(&format); + + /* evaluate width field */ + width = 0U; + if (is_digit_(*format)) { + /* + * Note: If the width is negative, we've already parsed its + * sign character '-' as a FLAG_LEFT + */ + width = (printf_size_t) atou_(&format); + } + else if (*format == '*') { + const int w = va_arg(args, int); + if (w < 0) { + flags |= FLAGS_LEFT; /* reverse padding */ + width = (printf_size_t)-w; + } + else { + width = (printf_size_t)w; + } + ADVANCE_IN_FORMAT_STRING(format); + } + + /* evaluate precision field */ + precision = 0U; + if (*format == '.') { + flags |= FLAGS_PRECISION; + ADVANCE_IN_FORMAT_STRING(format); + if (*format == '-') { + do { + ADVANCE_IN_FORMAT_STRING(format); + } while (is_digit_(*format)); + flags &= ~FLAGS_PRECISION; + } + else if (is_digit_(*format)) { + precision = atou_(&format); + } + else if (*format == '*') { + const int precision_ = va_arg(args, int); + if (precision_ < 0) { + flags &= ~FLAGS_PRECISION; + } + else { + precision = precision_ > 0 ? (printf_size_t) precision_ : 0U; + } + ADVANCE_IN_FORMAT_STRING(format); + } + } + + /* evaluate length field */ + switch (*format) { +#if PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS + case 'I' : { + ADVANCE_IN_FORMAT_STRING(format); + /* Greedily parse for size in bits: 8, 16, 32 or 64 */ + switch(*format) { +#ifndef PRINTF_NO_8_BIT_TYPE + case '8': flags |= FLAGS_INT8; + ADVANCE_IN_FORMAT_STRING(format); + break; +#endif +#ifndef PRINTF_NO_16_BIT_TYPE + case '1': + ADVANCE_IN_FORMAT_STRING(format); + if (*format == '6') { format++; flags |= FLAGS_INT16; } + break; +#endif +#ifndef PRINTF_NO_32_BIT_TYPE + case '3': + ADVANCE_IN_FORMAT_STRING(format); + if (*format == '2') { ADVANCE_IN_FORMAT_STRING(format); flags |= FLAGS_INT32; } + break; +#endif +#ifndef PRINTF_NO_64_BIT_TYPE + case '6': + ADVANCE_IN_FORMAT_STRING(format); + if (*format == '4') { ADVANCE_IN_FORMAT_STRING(format); flags |= FLAGS_INT64; } + break; +#endif + default: break; + } + break; + } +#endif /* PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS */ + case 'l' : + flags |= FLAGS_LONG; + ADVANCE_IN_FORMAT_STRING(format); + if (*format == 'l') { + flags |= FLAGS_LONG_LONG; + ADVANCE_IN_FORMAT_STRING(format); + } + break; + case 'L' : + flags |= FLAGS_LONG_DOUBLE; + ADVANCE_IN_FORMAT_STRING(format); + break; + case 'h' : + flags |= FLAGS_SHORT; + ADVANCE_IN_FORMAT_STRING(format); + if (*format == 'h') { + flags |= FLAGS_CHAR; + ADVANCE_IN_FORMAT_STRING(format); + } + break; + case 't' : + flags |= (sizeof(ptrdiff_t) <= sizeof(int) ) ? FLAGS_INT : (sizeof(ptrdiff_t) == sizeof(long)) ? FLAGS_LONG : FLAGS_LONG_LONG; + ADVANCE_IN_FORMAT_STRING(format); + break; + case 'j' : + flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + ADVANCE_IN_FORMAT_STRING(format); + break; + case 'z' : + flags |= (sizeof(size_t) <= sizeof(int) ) ? FLAGS_INT : (sizeof(size_t) == sizeof(long)) ? FLAGS_LONG : FLAGS_LONG_LONG; + ADVANCE_IN_FORMAT_STRING(format); + break; + default: + break; + } + + /* evaluate specifier */ + switch (*format) { + case 'd' : + case 'i' : + case 'u' : + case 'x' : + case 'X' : + case 'o' : + case 'b' : { + numeric_base_t base; + + if (*format == 'd' || *format == 'i') { + flags |= FLAGS_SIGNED; + } + + if (*format == 'x' || *format == 'X') { + base = BASE_HEX; + } + else if (*format == 'o') { + base = BASE_OCTAL; + } + else if (*format == 'b') { + base = BASE_BINARY; + } + else { + base = BASE_DECIMAL; + flags &= ~FLAGS_HASH; /* decimal integers have no alternative presentation */ + } + + if (*format == 'X') { + flags |= FLAGS_UPPERCASE; + } + + format++; + /* ignore '0' flag when precision is given */ + if (flags & FLAGS_PRECISION) { + flags &= ~FLAGS_ZEROPAD; + } + + if (flags & FLAGS_SIGNED) { + /* A signed specifier: d, i or possibly I + bit size if enabled */ + + if (flags & FLAGS_LONG_LONG) { +#if PRINTF_SUPPORT_LONG_LONG + const long long value = va_arg(args, long long); + print_integer(output, ABS_FOR_PRINTING(value), value < 0, base, precision, width, flags); +#endif + } + else if (flags & FLAGS_LONG) { + const long value = va_arg(args, long); + print_integer(output, ABS_FOR_PRINTING(value), value < 0, base, precision, width, flags); + } + else { + /* + * We never try to interpret the argument as something potentially-smaller than int, + * due to integer promotion rules: Even if the user passed a short int, short unsigned + * etc. - these will come in after promotion, as int's (or unsigned for the case of + * short unsigned when it has the same size as int) + */ + const int value = + (flags & FLAGS_CHAR) ? (signed char) va_arg(args, int) : + (flags & FLAGS_SHORT) ? (short int) va_arg(args, int) : + va_arg(args, int); + print_integer(output, ABS_FOR_PRINTING(value), value < 0, base, precision, width, flags); + } + } + else { + /* An unsigned specifier: u, x, X, o, b */ + + flags &= ~(FLAGS_PLUS | FLAGS_SPACE); + + if (flags & FLAGS_LONG_LONG) { +#if PRINTF_SUPPORT_LONG_LONG + print_integer(output, (printf_unsigned_value_t) va_arg(args, unsigned long long), false, base, precision, width, flags); +#endif + } + else if (flags & FLAGS_LONG) { + print_integer(output, (printf_unsigned_value_t) va_arg(args, unsigned long), false, base, precision, width, flags); + } + else { + const unsigned int value = + (flags & FLAGS_CHAR) ? (unsigned char)va_arg(args, unsigned int) : + (flags & FLAGS_SHORT) ? (unsigned short int)va_arg(args, unsigned int) : + va_arg(args, unsigned int); + print_integer(output, (printf_unsigned_value_t) value, false, base, precision, width, flags); + } + } + break; + } +#if PRINTF_SUPPORT_DECIMAL_SPECIFIERS + case 'f' : + case 'F' : { + floating_point_t value = (flags & FLAGS_LONG_DOUBLE ? (floating_point_t) va_arg(args, long double) : (floating_point_t) va_arg(args, double)); + if (*format == 'F') flags |= FLAGS_UPPERCASE; + print_floating_point(output, value, precision, width, flags, PRINTF_PREFER_DECIMAL); + format++; + break; + } +#endif +#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS + case 'e': + case 'E': + case 'g': + case 'G': { + floating_point_t value = (flags & FLAGS_LONG_DOUBLE ? (floating_point_t) va_arg(args, long double) : (floating_point_t) va_arg(args, double)); + if ((*format == 'g')||(*format == 'G')) flags |= FLAGS_ADAPT_EXP; + if ((*format == 'E')||(*format == 'G')) flags |= FLAGS_UPPERCASE; + print_floating_point(output, value, precision, width, flags, PRINTF_PREFER_EXPONENTIAL); + format++; + break; + } +#endif /* PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS */ + case 'c' : { + printf_size_t l = 1U; + /* pre padding */ + if (!(flags & FLAGS_LEFT)) { + while (l++ < width) { + putchar_via_gadget(output, ' '); + } + } + /* char output */ + putchar_via_gadget(output, (char) va_arg(args, int) ); + /* post padding */ + if (flags & FLAGS_LEFT) { + while (l++ < width) { + putchar_via_gadget(output, ' '); + } + } + format++; + break; + } + + case 's' : { + const char* p = va_arg(args, char*); + if (p == NULL) { + out_rev_(output, ")llun(", 6, width, flags); + } + else { + printf_size_t l = strnlen_s_(p, precision ? precision : PRINTF_MAX_POSSIBLE_BUFFER_SIZE); + /* pre padding */ + if (flags & FLAGS_PRECISION) { + l = (l < precision ? l : precision); + } + if (!(flags & FLAGS_LEFT)) { + while (l++ < width) { + putchar_via_gadget(output, ' '); + } + } + /* string output */ + while ((*p != 0) && (!(flags & FLAGS_PRECISION) || precision)) { + putchar_via_gadget(output, *(p++)); + --precision; /* Note: If the precision was not set, the ignored `precision` value will underflow; this is defined behavior. */ + } + /* post padding */ + if (flags & FLAGS_LEFT) { + while (l++ < width) { + putchar_via_gadget(output, ' '); + } + } + } + format++; + break; + } + + case 'p' : { + uintptr_t value = (uintptr_t)va_arg(args, void*); + if (value == (uintptr_t) NULL) { + out_rev_(output, ")lin(", 5, width, flags); + } + else { + flags |= FLAGS_ZEROPAD | FLAGS_POINTER; + width = ((width > 0) ? width : (sizeof(void*) * 2U)) + 2U; /* 2 hex chars per byte + the "0x" prefix */ + print_integer(output, (printf_unsigned_value_t) value, false, BASE_HEX, precision, width, flags); + } + format++; + break; + } + + case '%' : + putchar_via_gadget(output, '%'); + format++; + break; + + /* + * Many people prefer to disable support for %n, as it lets the caller + * engineer a write to an arbitrary location, of a value the caller + * effectively controls - which could be a security concern in some cases. + */ +#if PRINTF_SUPPORT_WRITEBACK_SPECIFIER + case 'n' : { + if (flags & FLAGS_CHAR) *(va_arg(args, char*)) = (char) output->pos; + else if (flags & FLAGS_SHORT) *(va_arg(args, short*)) = (short) output->pos; + else if (flags & FLAGS_LONG) *(va_arg(args, long*)) = (long) output->pos; +#if PRINTF_SUPPORT_LONG_LONG + else if (flags & FLAGS_LONG_LONG) *(va_arg(args, long long*)) = (long long int) output->pos; +#endif /* PRINTF_SUPPORT_LONG_LONG */ + else *(va_arg(args, int*)) = (int) output->pos; + format++; + break; + } +#endif /* PRINTF_SUPPORT_WRITEBACK_SPECIFIER */ + + default : + putchar_via_gadget(output, *format); + format++; + break; + } + } +} + +/* internal vsnprintf - used for implementing _all library functions */ +static int vsnprintf_impl(output_gadget_t* output, const char* format, va_list args) +{ + /* + * Note: The library only calls vsnprintf_impl() with output->pos being 0. However, it is + * possible to call this function with a non-zero pos value for some "remedial printing". + */ + format_string_loop(output, format, args); + + /* termination */ + append_termination_via_gadget(output); + + /* return written chars without terminating \0 */ + return (int)output->pos; +} + +/*===========================================================================*/ + +int vprintf_(const char* format, va_list arg) +{ + output_gadget_t gadget = extern_putchar_gadget(); + return vsnprintf_impl(&gadget, format, arg); +} + +int vsnprintf_(char* s, size_t n, const char* format, va_list arg) +{ + output_gadget_t gadget = buffer_gadget(s, n); + return vsnprintf_impl(&gadget, format, arg); +} + +int vsprintf_(char* s, const char* format, va_list arg) +{ + return vsnprintf_(s, PRINTF_MAX_POSSIBLE_BUFFER_SIZE, format, arg); +} + +int vfctprintf(void (*out)(char c, void* extra_arg), void* extra_arg, const char* format, va_list arg) +{ + output_gadget_t gadget; + if (out == NULL) { return 0; } + gadget = function_gadget(out, extra_arg); + return vsnprintf_impl(&gadget, format, arg); +} + +int printf_(const char* format, ...) +{ + int ret; + va_list args; + va_start(args, format); + ret = vprintf_(format, args); + va_end(args); + return ret; +} + +int sprintf_(char* s, const char* format, ...) +{ + int ret; + va_list args; + va_start(args, format); + ret = vsprintf_(s, format, args); + va_end(args); + return ret; +} + +int snprintf_(char* s, size_t n, const char* format, ...) +{ + int ret; + va_list args; + va_start(args, format); + ret = vsnprintf_(s, n, format, args); + va_end(args); + return ret; +} + +int fctprintf(void (*out)(char c, void* extra_arg), void* extra_arg, const char* format, ...) +{ + int ret; + va_list args; + va_start(args, format); + ret = vfctprintf(out, extra_arg, format, args); + va_end(args); + return ret; +} + diff --git a/compat/stdio/printf.h b/compat/stdio/printf.h new file mode 100644 index 000000000..413c142fe --- /dev/null +++ b/compat/stdio/printf.h @@ -0,0 +1,234 @@ +/** + * @author (c) Eyal Rozenberg + * 2021-2024, Haifa, Palestine/Israel + * @author (c) Marco Paland (info@paland.com) + * 2014-2019, PALANDesign Hannover, Germany + * + * @note Others have made smaller contributions to this file: see the + * contributors page at https://github.com/eyalroz/printf/graphs/contributors + * or ask one of the authors. + * + * @brief Small stand-alone implementation of the printf family of functions + * (`(v)printf`, `(v)s(n)printf` etc., geared towards use on embedded systems + * with a very limited resources. + * + * @note the implementations are thread-safe; re-entrant; use no functions from + * the standard library; and do not dynamically allocate any memory. + * + * @license The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 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 OR COPYRIGHT HOLDERS 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. + */ + +#ifndef PRINTF_H_ +#define PRINTF_H_ + +#if defined(PRINTF_INCLUDE_CONFIG_H) && PRINTF_INCLUDE_CONFIG_H +#include "printf_config.h" +#endif + +#ifdef __cplusplus +# include +# include +extern "C" { +#else +# include +# include +#endif + +#ifdef __GNUC__ +# if ((__GNUC__ == 4 && __GNUC_MINOR__>= 4) || __GNUC__ > 4) +# define ATTR_PRINTF(one_based_format_index, first_arg) \ +__attribute__((format(gnu_printf, (one_based_format_index), (first_arg)))) +# else +# define ATTR_PRINTF(one_based_format_index, first_arg) \ +__attribute__((format(printf, (one_based_format_index), (first_arg)))) +# endif +# define ATTR_VPRINTF(one_based_format_index) \ +ATTR_PRINTF((one_based_format_index), 0) +#else +# define ATTR_PRINTF(one_based_format_index, first_arg) +# define ATTR_VPRINTF(one_based_format_index) +#endif + +#if PRINTF_ALIAS_STANDARD_FUNCTION_NAMES_HARD +# define printf_ printf +# define sprintf_ sprintf +# define vsprintf_ vsprintf +# define snprintf_ snprintf +# define vsnprintf_ vsnprintf +# define vprintf_ vprintf +#endif + +/* + * If you want to include this implementation file directly rather than + * link against it, this will let you control the functions' visibility, + * e.g. make them static so as not to clash with other objects also + * using them. + */ +#ifndef PRINTF_VISIBILITY +#define PRINTF_VISIBILITY +#endif + +/** + * Prints/send a single character to some opaque output entity + * + * @note This function is not implemented by the library, only declared; you + * must provide an implementation if you wish to use the @ref printf / @ref + * vprintf function (and possibly for linking against the library, if your + * toolchain does not support discarding unused functions) + * + * @note The output could be as simple as a wrapper for the `write()` system + * call on a Unix-like * system, or even libc's @ref putchar , for replicating + * actual functionality of libc's @ref printf * function; but on an embedded + * system it may involve interaction with a special output device, like a UART, + * etc. + * + * @note in libc's @ref putchar, the parameter type is an int; this was intended + * to support the representation of either a proper character or EOF in a + * variable - but this is really not meaningful to pass into @ref putchar and is + * discouraged today. See further discussion in: + * @link https://stackoverflow.com/q/17452847/1593077 + * + * @param c the single character to print + */ +PRINTF_VISIBILITY +void putchar_(char c); + + +/** + * An implementation of the C standard's printf/vprintf + * + * @note you must implement a @ref putchar_ function for using this function - + * it invokes @ref putchar_ * rather than directly performing any I/O (which + * insulates it from any dependence on the operating system * and external + * libraries). + * + * @param format A string specifying the format of the output, with %-marked + * specifiers of how to interpret additional arguments. + * @param arg Additional arguments to the function, one for each %-specifier in + * @p format + * @return The number of characters written into @p s, not counting the + * terminating null character + */ +/* @{ */ +PRINTF_VISIBILITY +int printf_(const char* format, ...) ATTR_PRINTF(1, 2); +PRINTF_VISIBILITY +int vprintf_(const char* format, va_list arg) ATTR_VPRINTF(1); +/* @} */ + + +/** + * An implementation of the C standard's sprintf/vsprintf + * + * @note For safety reasons (the potential for exceeding the buffer bounbds), + * please consider using the size-constrained variant, @ref snprintf / + * @ref vsnprintf, instead. + * + * @param s An array in which to store the formatted string. It must be large + * enough to fit the formatted output! + * @param format A string specifying the format of the output, with %-marked + * specifiers of how to interpret additional arguments + * @param arg Additional arguments to the function, one for each specifier in + * @p format + * @return The number of characters written into @p s, not counting the + * terminating null character + */ +/* @{ */ +PRINTF_VISIBILITY +int sprintf_(char* s, const char* format, ...) ATTR_PRINTF(2, 3); +PRINTF_VISIBILITY +int vsprintf_(char* s, const char* format, va_list arg) ATTR_VPRINTF(2); +/* @} */ + + +/** + * An implementation of the C standard's snprintf/vsnprintf + * + * @param s An array in which to store the formatted string. It must be large + * enough to fit either the entire formatted output, or at least @p n + * characters. Alternatively, it can be NULL, in which case nothing will + * be printed, and only the number of characters which _could_ have been + * printed is tallied and returned. + * @param n The maximum number of characters to write to the array, including + * a terminating null character + * @param format A string specifying the format of the output, with %-marked + * specifiers of how to interpret additional arguments. + * @param arg Additional arguments to the function, one for each specifier in + * @p format + * @return The number of characters that COULD have been written into @p s, not + * counting the terminating null character. A value equal or larger than + * @p n indicates truncation. Only when the returned value is non-negative + * and less than @p n, the null-terminated string has been fully and + * successfully printed. + */ +/* @{ */ +PRINTF_VISIBILITY +int snprintf_(char* s, size_t count, const char* format, ...) ATTR_PRINTF(3, 4); +PRINTF_VISIBILITY +int vsnprintf_(char* s, size_t count, const char* format, va_list arg) ATTR_VPRINTF(3); +/* @} */ + +/** + * printf/vprintf with user-specified output function + * + * An alternative to @ref printf_, in which the output function is specified + * dynamically (rather than @ref putchar_ being used) + * + * @param out An output function which takes one character and a type-erased + * additional parameters + * @param extra_arg The type-erased argument to pass to the output function @p + * out with each call + * @param format A string specifying the format of the output, with %-marked + * specifiers of how to interpret additional arguments. + * @param arg Additional arguments to the function, one for each specifier in + * @p format + * @return The number of characters for which the output f unction was invoked, + * not counting the terminating null character + * + */ +PRINTF_VISIBILITY +int fctprintf(void (*out)(char c, void* extra_arg), void* extra_arg, const char* format, ...) ATTR_PRINTF(3, 4); +PRINTF_VISIBILITY +int vfctprintf(void (*out)(char c, void* extra_arg), void* extra_arg, const char* format, va_list arg) ATTR_VPRINTF(3); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#if PRINTF_ALIAS_STANDARD_FUNCTION_NAMES_HARD +# undef printf_ +# undef sprintf_ +# undef vsprintf_ +# undef snprintf_ +# undef vsnprintf_ +# undef vprintf_ +#else +#if PRINTF_ALIAS_STANDARD_FUNCTION_NAMES_SOFT +# define printf printf_ +# define sprintf sprintf_ +# define vsprintf vsprintf_ +# define snprintf snprintf_ +# define vsnprintf vsnprintf_ +# define vprintf vprintf_ +#endif +#endif + +#endif /* PRINTF_H_ */ diff --git a/src/stdio_compat.h b/src/stdio_compat.h index 702c22678..6984b89e4 100644 --- a/src/stdio_compat.h +++ b/src/stdio_compat.h @@ -4,8 +4,8 @@ #include #ifdef NO_SNPRINTF -#include -#define snprintf npf_snprintf +#define PRINTF_ALIAS_STANDARD_FUNCTION_NAMES_HARD 1 +#include #endif #endif /* _BS_COMPAT_STDIO_H_ */ From 0bbd99ce6a6182ee6fa7aa3591782a89bc7e9fb6 Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 12:34:42 -0400 Subject: [PATCH 32/57] better message --- compat/configure.cmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compat/configure.cmd b/compat/configure.cmd index e77f46bfa..e557555c4 100644 --- a/compat/configure.cmd +++ b/compat/configure.cmd @@ -90,7 +90,7 @@ if errorlevel 1 ( >>tmp\test.c echo. int b = a; >>tmp\test.c echo. return b; >>tmp\test.c echo.} -call :check if mixed declarations and code are supported +call :check if C supports mixed declarations and code if errorlevel 1 ( >>config.bat echo set CC_COMPILE=%%CC%% /TP ) From cb05e547f4c8644aa785231dbc2a4f5c8642c72c Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 12:40:51 -0400 Subject: [PATCH 33/57] bullshit --- compat/stdint/stdint.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/compat/stdint/stdint.h b/compat/stdint/stdint.h index b9f7c7a5e..c02b5159e 100644 --- a/compat/stdint/stdint.h +++ b/compat/stdint/stdint.h @@ -10,6 +10,12 @@ #define int64_t __bs_int64_t #endif +#ifdef _MSC_VER +/* MSVC used to define intptr_t here, before it had stdint.h */ +#include +#define intptr_t __bs_intptr_t +#endif + typedef signed char int8_t; typedef signed short int16_t; typedef signed int int32_t; @@ -18,7 +24,6 @@ typedef unsigned char uint8_t; typedef unsigned short uint16_t; typedef unsigned int uint32_t; typedef unsigned long long uint64_t; -typedef int8_t int_fast8_t; typedef long intptr_t; #endif /* _BS_STDINT_H_ */ From ed4ad004b6ed462ecf4f5f1e27d84f0ab1534bf4 Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 13:05:38 -0400 Subject: [PATCH 34/57] fix --- compat/stdio/printf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compat/stdio/printf.c b/compat/stdio/printf.c index 824e4699d..5e5706ad1 100644 --- a/compat/stdio/printf.c +++ b/compat/stdio/printf.c @@ -45,7 +45,7 @@ #include "printf_config.h" #endif -#include +#include "printf.h" #ifdef __cplusplus #include From b9ba9bc82ceda8d8b0d45f6a34e2adb0dce71935 Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 13:14:45 -0400 Subject: [PATCH 35/57] fixes --- compat/stdint/stdint.h | 6 ++++++ compat/stdio/printf.c | 5 ----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/compat/stdint/stdint.h b/compat/stdint/stdint.h index c02b5159e..3af4bd8a6 100644 --- a/compat/stdint/stdint.h +++ b/compat/stdint/stdint.h @@ -20,10 +20,16 @@ typedef signed char int8_t; typedef signed short int16_t; typedef signed int int32_t; typedef signed long long int64_t; + typedef unsigned char uint8_t; typedef unsigned short uint16_t; typedef unsigned int uint32_t; typedef unsigned long long uint64_t; + +typedef int64_t int_fast64_t; + +typedef int64_t intmax_t; + typedef long intptr_t; #endif /* _BS_STDINT_H_ */ diff --git a/compat/stdio/printf.c b/compat/stdio/printf.c index 5e5706ad1..520cce747 100644 --- a/compat/stdio/printf.c +++ b/compat/stdio/printf.c @@ -47,14 +47,9 @@ #include "printf.h" -#ifdef __cplusplus -#include -#include -#else #include #include #include -#endif /* __cplusplus */ #if !(defined(__cplusplus) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L)) /* C90 */ From 5d128753960284f38e86c616a6c78e839297391b Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 13:19:15 -0400 Subject: [PATCH 36/57] fix --- src/stdio_compat.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/stdio_compat.h b/src/stdio_compat.h index 6984b89e4..949a5dd6e 100644 --- a/src/stdio_compat.h +++ b/src/stdio_compat.h @@ -4,8 +4,9 @@ #include #ifdef NO_SNPRINTF -#define PRINTF_ALIAS_STANDARD_FUNCTION_NAMES_HARD 1 #include +#define snprintf snprintf_ +#define vsnprintf vsnprintf_ #endif #endif /* _BS_COMPAT_STDIO_H_ */ From f2ce3dde70a34caa0eee698e80eb5ef0bde8b201 Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 13:26:23 -0400 Subject: [PATCH 37/57] trim printf --- compat/stdio/printf.c | 69 ------------------------ compat/stdio/printf.h | 121 +----------------------------------------- src/stdio_compat.h | 3 +- 3 files changed, 3 insertions(+), 190 deletions(-) diff --git a/compat/stdio/printf.c b/compat/stdio/printf.c index 520cce747..9be2f9858 100644 --- a/compat/stdio/printf.c +++ b/compat/stdio/printf.c @@ -61,12 +61,8 @@ #endif /* !(defined(__cplusplus) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L)) */ #if PRINTF_ALIAS_STANDARD_FUNCTION_NAMES_HARD -# define printf_ printf -# define sprintf_ sprintf -# define vsprintf_ vsprintf # define snprintf_ snprintf # define vsnprintf_ vsnprintf -# define vprintf_ vprintf #endif /* PRINTF_ALIAS_STANDARD_FUNCTION_NAMES_HARD */ @@ -425,16 +421,6 @@ static inline void append_termination_via_gadget(output_gadget_t* gadget) gadget->buffer[null_char_pos] = '\0'; } -/* - * We can't use putchar_ as is, since our output gadget - * only takes pointers to functions with an extra argument - */ -static inline void putchar_wrapper(char c, void* unused) -{ - (void) unused; - putchar_(c); -} - static inline output_gadget_t discarding_gadget(void) { output_gadget_t gadget; @@ -467,11 +453,6 @@ static inline output_gadget_t function_gadget(void (*function)(char, void*), voi return result; } -static inline output_gadget_t extern_putchar_gadget(void) -{ - return function_gadget(putchar_wrapper, NULL); -} - /* * internal secure strlen * @return The length of the string (excluding the terminating 0) limited by 'maxsize' @@ -1591,51 +1572,12 @@ static int vsnprintf_impl(output_gadget_t* output, const char* format, va_list a /*===========================================================================*/ -int vprintf_(const char* format, va_list arg) -{ - output_gadget_t gadget = extern_putchar_gadget(); - return vsnprintf_impl(&gadget, format, arg); -} - int vsnprintf_(char* s, size_t n, const char* format, va_list arg) { output_gadget_t gadget = buffer_gadget(s, n); return vsnprintf_impl(&gadget, format, arg); } -int vsprintf_(char* s, const char* format, va_list arg) -{ - return vsnprintf_(s, PRINTF_MAX_POSSIBLE_BUFFER_SIZE, format, arg); -} - -int vfctprintf(void (*out)(char c, void* extra_arg), void* extra_arg, const char* format, va_list arg) -{ - output_gadget_t gadget; - if (out == NULL) { return 0; } - gadget = function_gadget(out, extra_arg); - return vsnprintf_impl(&gadget, format, arg); -} - -int printf_(const char* format, ...) -{ - int ret; - va_list args; - va_start(args, format); - ret = vprintf_(format, args); - va_end(args); - return ret; -} - -int sprintf_(char* s, const char* format, ...) -{ - int ret; - va_list args; - va_start(args, format); - ret = vsprintf_(s, format, args); - va_end(args); - return ret; -} - int snprintf_(char* s, size_t n, const char* format, ...) { int ret; @@ -1645,14 +1587,3 @@ int snprintf_(char* s, size_t n, const char* format, ...) va_end(args); return ret; } - -int fctprintf(void (*out)(char c, void* extra_arg), void* extra_arg, const char* format, ...) -{ - int ret; - va_list args; - va_start(args, format); - ret = vfctprintf(out, extra_arg, format, args); - va_end(args); - return ret; -} - diff --git a/compat/stdio/printf.h b/compat/stdio/printf.h index 413c142fe..3a067f6b3 100644 --- a/compat/stdio/printf.h +++ b/compat/stdio/printf.h @@ -43,14 +43,8 @@ #include "printf_config.h" #endif -#ifdef __cplusplus -# include -# include -extern "C" { -#else -# include -# include -#endif +#include +#include #ifdef __GNUC__ # if ((__GNUC__ == 4 && __GNUC_MINOR__>= 4) || __GNUC__ > 4) @@ -68,12 +62,8 @@ ATTR_PRINTF((one_based_format_index), 0) #endif #if PRINTF_ALIAS_STANDARD_FUNCTION_NAMES_HARD -# define printf_ printf -# define sprintf_ sprintf -# define vsprintf_ vsprintf # define snprintf_ snprintf # define vsnprintf_ vsnprintf -# define vprintf_ vprintf #endif /* @@ -86,78 +76,6 @@ ATTR_PRINTF((one_based_format_index), 0) #define PRINTF_VISIBILITY #endif -/** - * Prints/send a single character to some opaque output entity - * - * @note This function is not implemented by the library, only declared; you - * must provide an implementation if you wish to use the @ref printf / @ref - * vprintf function (and possibly for linking against the library, if your - * toolchain does not support discarding unused functions) - * - * @note The output could be as simple as a wrapper for the `write()` system - * call on a Unix-like * system, or even libc's @ref putchar , for replicating - * actual functionality of libc's @ref printf * function; but on an embedded - * system it may involve interaction with a special output device, like a UART, - * etc. - * - * @note in libc's @ref putchar, the parameter type is an int; this was intended - * to support the representation of either a proper character or EOF in a - * variable - but this is really not meaningful to pass into @ref putchar and is - * discouraged today. See further discussion in: - * @link https://stackoverflow.com/q/17452847/1593077 - * - * @param c the single character to print - */ -PRINTF_VISIBILITY -void putchar_(char c); - - -/** - * An implementation of the C standard's printf/vprintf - * - * @note you must implement a @ref putchar_ function for using this function - - * it invokes @ref putchar_ * rather than directly performing any I/O (which - * insulates it from any dependence on the operating system * and external - * libraries). - * - * @param format A string specifying the format of the output, with %-marked - * specifiers of how to interpret additional arguments. - * @param arg Additional arguments to the function, one for each %-specifier in - * @p format - * @return The number of characters written into @p s, not counting the - * terminating null character - */ -/* @{ */ -PRINTF_VISIBILITY -int printf_(const char* format, ...) ATTR_PRINTF(1, 2); -PRINTF_VISIBILITY -int vprintf_(const char* format, va_list arg) ATTR_VPRINTF(1); -/* @} */ - - -/** - * An implementation of the C standard's sprintf/vsprintf - * - * @note For safety reasons (the potential for exceeding the buffer bounbds), - * please consider using the size-constrained variant, @ref snprintf / - * @ref vsnprintf, instead. - * - * @param s An array in which to store the formatted string. It must be large - * enough to fit the formatted output! - * @param format A string specifying the format of the output, with %-marked - * specifiers of how to interpret additional arguments - * @param arg Additional arguments to the function, one for each specifier in - * @p format - * @return The number of characters written into @p s, not counting the - * terminating null character - */ -/* @{ */ -PRINTF_VISIBILITY -int sprintf_(char* s, const char* format, ...) ATTR_PRINTF(2, 3); -PRINTF_VISIBILITY -int vsprintf_(char* s, const char* format, va_list arg) ATTR_VPRINTF(2); -/* @} */ - /** * An implementation of the C standard's snprintf/vsnprintf @@ -186,48 +104,13 @@ PRINTF_VISIBILITY int vsnprintf_(char* s, size_t count, const char* format, va_list arg) ATTR_VPRINTF(3); /* @} */ -/** - * printf/vprintf with user-specified output function - * - * An alternative to @ref printf_, in which the output function is specified - * dynamically (rather than @ref putchar_ being used) - * - * @param out An output function which takes one character and a type-erased - * additional parameters - * @param extra_arg The type-erased argument to pass to the output function @p - * out with each call - * @param format A string specifying the format of the output, with %-marked - * specifiers of how to interpret additional arguments. - * @param arg Additional arguments to the function, one for each specifier in - * @p format - * @return The number of characters for which the output f unction was invoked, - * not counting the terminating null character - * - */ -PRINTF_VISIBILITY -int fctprintf(void (*out)(char c, void* extra_arg), void* extra_arg, const char* format, ...) ATTR_PRINTF(3, 4); -PRINTF_VISIBILITY -int vfctprintf(void (*out)(char c, void* extra_arg), void* extra_arg, const char* format, va_list arg) ATTR_VPRINTF(3); - -#ifdef __cplusplus -} /* extern "C" */ -#endif - #if PRINTF_ALIAS_STANDARD_FUNCTION_NAMES_HARD -# undef printf_ -# undef sprintf_ -# undef vsprintf_ # undef snprintf_ # undef vsnprintf_ -# undef vprintf_ #else #if PRINTF_ALIAS_STANDARD_FUNCTION_NAMES_SOFT -# define printf printf_ -# define sprintf sprintf_ -# define vsprintf vsprintf_ # define snprintf snprintf_ # define vsnprintf vsnprintf_ -# define vprintf vprintf_ #endif #endif diff --git a/src/stdio_compat.h b/src/stdio_compat.h index 949a5dd6e..fb30a9f69 100644 --- a/src/stdio_compat.h +++ b/src/stdio_compat.h @@ -4,9 +4,8 @@ #include #ifdef NO_SNPRINTF +#define PRINTF_ALIAS_STANDARD_FUNCTION_NAMES_SOFT 1 #include -#define snprintf snprintf_ -#define vsnprintf vsnprintf_ #endif #endif /* _BS_COMPAT_STDIO_H_ */ From bdff8c3322c1e8cfb941a5b04ebcd9678d2131bd Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 13:56:22 -0400 Subject: [PATCH 38/57] msvc --- src/spatial_grid.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/spatial_grid.h b/src/spatial_grid.h index acbc1c0c2..df1e73288 100644 --- a/src/spatial_grid.h +++ b/src/spatial_grid.h @@ -60,7 +60,12 @@ static inline SpatialGridRange SpatialGrid_computeCellRange(SpatialGrid* grid, G if (minGridY > grid->gridHeight - 1) minGridY = grid->gridHeight - 1; if (maxGridX > grid->gridWidth - 1) maxGridX = grid->gridWidth - 1; if (maxGridY > grid->gridHeight - 1) maxGridY = grid->gridHeight - 1; - return (SpatialGridRange){ (uint16_t)minGridX, (uint16_t)minGridY, (uint16_t)maxGridX, (uint16_t)maxGridY }; + SpatialGridRange ret; + ret.minGridX = minGridX; + ret.minGridY = minGridY; + ret.maxGridX = maxGridX; + ret.maxGridY = maxGridY; + return ret; } static inline bool SpatialGrid_instanceOverlapsRange(Instance* instance, SpatialGridRange range) { From 8c1342dd37bd9f70d8cbb7bea4b53681fb1492c7 Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 14:07:33 -0400 Subject: [PATCH 39/57] bullshit --- src/vm.c | 1 + src/vm.h | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/src/vm.c b/src/vm.c index 72b3bcb32..dd7a50631 100644 --- a/src/vm.c +++ b/src/vm.c @@ -4394,6 +4394,7 @@ void VM_disassemble(VMContext* ctx, int32_t codeIndex) { printf("\n"); } +#undef VM_registerBuiltin void VM_registerBuiltin(VMContext* ctx, const char* name, BuiltinFunc func) { requireMessageFormatted(__FILE__, __LINE__, shgeti(ctx->builtinMap, name) == -1, "Trying to register an already registered builtin function! Function Name: %s", name); shput(ctx->builtinMap, (char*) name, func); diff --git a/src/vm.h b/src/vm.h index 2fa33b73e..617280b5c 100644 --- a/src/vm.h +++ b/src/vm.h @@ -310,6 +310,11 @@ void VM_disassemble(VMContext* ctx, int32_t codeIndex); void VM_printOpcodeProfilerReport(const VMContext* ctx); #endif void VM_registerBuiltin(VMContext* ctx, const char* name, BuiltinFunc func); +#if defined(_MSC_VER) && !defined(__clang__) +// Some versions of MSVC complain that the functions aren't the right type because of struct bullshit. +// We guard this behind _MSC_VER to keep type checking on other compilers to find legitimate type mismatch errors. +#define VM_registerBuiltin(ctx,name,func) VM_registerBuiltin(ctx,name,(BuiltinFunc)func) +#endif BuiltinFunc VM_findBuiltin(VMContext* ctx, const char* name); char* VM_getVariableNameByVarId(VMContext* ctx, int32_t varId); From f5e456d4f037a6edbffad9e41203c7ef1380831f Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 14:13:38 -0400 Subject: [PATCH 40/57] better --- build.cmd | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/build.cmd b/build.cmd index ceef69d5c..c46439991 100644 --- a/build.cmd +++ b/build.cmd @@ -124,10 +124,13 @@ if not defined CC_COMPILE set CC_COMPILE=%CC% if not exist build mkdir build +set OBJS= for %%f in (%SRCS%) do ( - %CC_COMPILE% %CFLAGS% %DEFINES% %INCLUDES% /c "%%f" /Fobuild\ + if not exist "build\%%~pf" mkdir "build\%%~pf" + %CC_COMPILE% %CFLAGS% %DEFINES% %INCLUDES% /c "%%f" /Fo"build\%%~pf%%~nf.obj" if errorlevel 1 exit /b 1 + set OBJS=!OBJS! "build\%%~pf%%~nf.obj" ) -%CC% %CFLAGS% build\*.obj /Febuild\butterscotch.exe /link %LIBS% %EXTRALIBS% +%CC% %CFLAGS% !OBJS! /Febuild\butterscotch.exe /link %LIBS% %EXTRALIBS% if errorlevel 1 exit /b 1 From 1e76f7bfedc2c60fa58847f9c43eb239dee0def2 Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 14:34:14 -0400 Subject: [PATCH 41/57] fix --- build.cmd | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/build.cmd b/build.cmd index c46439991..c3c95b8d1 100644 --- a/build.cmd +++ b/build.cmd @@ -126,10 +126,13 @@ if not exist build mkdir build set OBJS= for %%f in (%SRCS%) do ( - if not exist "build\%%~pf" mkdir "build\%%~pf" - %CC_COMPILE% %CFLAGS% %DEFINES% %INCLUDES% /c "%%f" /Fo"build\%%~pf%%~nf.obj" + set "p=%%~dpf" + set "n=%%~nf" + set "p=!p:%CD%\=!" + if not exist "build\!p!" mkdir "build\!p!" + %CC_COMPILE% %CFLAGS% %DEFINES% %INCLUDES% /c "%%f" /Fo"build\!p!!n!.obj" if errorlevel 1 exit /b 1 - set OBJS=!OBJS! "build\%%~pf%%~nf.obj" + set OBJS=!OBJS! "build\!p!!n!.obj" ) %CC% %CFLAGS% !OBJS! /Febuild\butterscotch.exe /link %LIBS% %EXTRALIBS% From e8b2543643a0ba9895feebba301044649b212e6b Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 14:51:03 -0400 Subject: [PATCH 42/57] msvc 9 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a4f0bc55f..b8ed73571 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ The following compilers have been tested to successfully build butterscotch, old * GCC 2.7 and up in C++ mode, and 3.0 and up in C99 mode * Clang 1.1 and up * TinyCC 0.9.27 and up -* MSVC 10.0 and up +* MSVC 9.0 and up ## Community Ports From b2c4ba893315762a1e99823d0511e41b80de8bc2 Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 15:05:06 -0400 Subject: [PATCH 43/57] snprintf check --- compat/configure.cmd | 2 +- compat/configure.sh | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/compat/configure.cmd b/compat/configure.cmd index e557555c4..895e1ce63 100644 --- a/compat/configure.cmd +++ b/compat/configure.cmd @@ -29,7 +29,7 @@ if errorlevel 1 ( >tmp\test.c echo.#include ^ >>tmp\test.c echo.int main(void^){ ->>tmp\test.c echo. char buf[64]; +>>tmp\test.c echo. char buf[8]; >>tmp\test.c echo. return snprintf(buf, sizeof(buf^), "test"^); >>tmp\test.c echo.} call :check for snprintf diff --git a/compat/configure.sh b/compat/configure.sh index d799fccc2..19abff56d 100644 --- a/compat/configure.sh +++ b/compat/configure.sh @@ -369,4 +369,18 @@ if ! check 'for getopt_long'; then include 'compat/getopt' fi +printf '%s' "\ +#include +int main(void){ + char buf[8]; + return snprintf(buf, sizeof(buf), "test"); +} +" > tmp/test.c + +if ! check 'for snprintf'; then + include 'compat/stdio' + define 'NO_SNPRINTF' + config 'SRCS += compat/stdio/printf.c' +fi + rm -f tmp/test.c tmp/a.out test.obj From 041fc47a2836f76c06867ab30812d14818b365fb Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 15:05:45 -0400 Subject: [PATCH 44/57] fix --- compat/configure.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compat/configure.sh b/compat/configure.sh index 19abff56d..c559abe5f 100644 --- a/compat/configure.sh +++ b/compat/configure.sh @@ -373,7 +373,7 @@ printf '%s' "\ #include int main(void){ char buf[8]; - return snprintf(buf, sizeof(buf), "test"); + return snprintf(buf, sizeof(buf), \"test\"); } " > tmp/test.c From 1489a4caf72f50e9daf66c422324fac8d4793261 Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 15:08:32 -0400 Subject: [PATCH 45/57] bruh --- compat/configure.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compat/configure.sh b/compat/configure.sh index c559abe5f..b5a9ec5c4 100644 --- a/compat/configure.sh +++ b/compat/configure.sh @@ -63,7 +63,7 @@ check() { output="$output_exe" [ -n "$nolink" ] && output="$compile_obj $output_obj" && nolink= printf 'cmd: %s\n' "$CC $cflags tmp/test.c ${output}tmp/a.out $*" >> tmp/config.log - if $CC $cflags tmp/test.c ${output}tmp/a.out "$@" >> tmp/config.log 2>&1; then + if MSYS2_ARG_CONV_EXCL='*' $CC $cflags tmp/test.c ${output}tmp/a.out "$@" >> tmp/config.log 2>&1; then printyes return 0 else From 8ddcab3aa73659aa5cebc8e976ba23f5ada202f5 Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 15:17:44 -0400 Subject: [PATCH 46/57] bruh --- Makefile | 2 +- compat/configure.sh | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index ae98734b1..45a26a592 100644 --- a/Makefile +++ b/Makefile @@ -214,7 +214,7 @@ build/butterscotch: $(OBJS) build/%.c.$(OBJ_EXT): %.c compat/config.mk $(if $(DISABLE_MMD),$(HEADERS)) @mkdir -p $(dir $@) @{ [ -z "$(NO_COLOR)" ] && [ -t 1 ]; } && printf " \033[1;32mCC\033[0m $<\n" || printf " CC $<\n" - $(V)$(_CC) $(DEFINES) $(INCLUDES) $(CFLAGS) $(DEPFLAGS) $(COMPILE_OBJ) $< $(OUTPUT_OBJ)$@ + $(V)MSYS2_ARG_CONV_EXCL='*' $(_CC) $(DEFINES) $(INCLUDES) $(CFLAGS) $(DEPFLAGS) $(COMPILE_OBJ) $< $(OUTPUT_OBJ)$@ clean: rm -rf build diff --git a/compat/configure.sh b/compat/configure.sh index b5a9ec5c4..e3dbe90ec 100644 --- a/compat/configure.sh +++ b/compat/configure.sh @@ -63,7 +63,8 @@ check() { output="$output_exe" [ -n "$nolink" ] && output="$compile_obj $output_obj" && nolink= printf 'cmd: %s\n' "$CC $cflags tmp/test.c ${output}tmp/a.out $*" >> tmp/config.log - if MSYS2_ARG_CONV_EXCL='*' $CC $cflags tmp/test.c ${output}tmp/a.out "$@" >> tmp/config.log 2>&1; then + export MSYS2_ARG_CONV_EXCL='*' + if $CC $cflags tmp/test.c ${output}tmp/a.out "$@" >> tmp/config.log 2>&1; then printyes return 0 else From 14803f1d4f78bce7048b8df5b0405d6d739454cb Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 15:29:11 -0400 Subject: [PATCH 47/57] fix --- compat/configure.sh | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/compat/configure.sh b/compat/configure.sh index e3dbe90ec..e50a2cef1 100644 --- a/compat/configure.sh +++ b/compat/configure.sh @@ -7,6 +7,8 @@ if [ -z "$CC" ]; then exit 1 fi +export MSYS2_ARG_CONV_EXCL='*' + # cd to the directory this script is in [ "${0%/*}" = "$0" ] && scriptroot="." || scriptroot="${0%/*}" cd "$scriptroot" @@ -63,7 +65,6 @@ check() { output="$output_exe" [ -n "$nolink" ] && output="$compile_obj $output_obj" && nolink= printf 'cmd: %s\n' "$CC $cflags tmp/test.c ${output}tmp/a.out $*" >> tmp/config.log - export MSYS2_ARG_CONV_EXCL='*' if $CC $cflags tmp/test.c ${output}tmp/a.out "$@" >> tmp/config.log 2>&1; then printyes return 0 @@ -90,14 +91,14 @@ int main(void){return 0;} " > tmp/test.c configlog 'checking the C compiler CLI syntax' -if $CC /nologo tmp/test.c /Fe:tmp/a.out >> tmp/config.log 2>&1; then +if $CC /nologo tmp/test.c /Fetmp/a.out >> tmp/config.log 2>&1; then printgreen 'msvc' syntax=msvc CC="$CC /nologo" cflags='/Oi-' # equivalent to -fno-builtin compile_obj='/c' - output_obj='/Fo:' - output_exe='/Fe:' + output_obj='/Fo' + output_exe='/Fe' config "OUTPUT_OBJ := $output_obj" config "OUTPUT_EXE := $output_exe" config 'MSVC := 1' From 76dd75d30112c3573f915618b1294a704ef04750 Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 15:36:22 -0400 Subject: [PATCH 48/57] fix msvc --- compat/configure.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/compat/configure.sh b/compat/configure.sh index e50a2cef1..ea7dea7e7 100644 --- a/compat/configure.sh +++ b/compat/configure.sh @@ -86,6 +86,12 @@ int main(void){return 0;} return $? } +ccname="${CC##*/}" +target="${ccname%-*}" +if [ "$ccname" = "$target" ]; then + target= +fi + printf '%s' "\ int main(void){return 0;} " > tmp/test.c @@ -138,12 +144,6 @@ else cross_compiling=1 fi -ccname="${CC##*/}" -target="${ccname%-*}" -if [ "$ccname" = "$target" ]; then - target= -fi - if [ -n "$target" ]; then configlog "checking for $target-pkg-config" if command -v "$target-pkg-config"; then From 8cda09eaabeca3572a784cf76d68b0272a79f11e Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 16:05:27 -0400 Subject: [PATCH 49/57] support old msvc in makefile --- Makefile | 6 +++--- compat/configure.sh | 27 +++++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 45a26a592..d81ac68ee 100644 --- a/Makefile +++ b/Makefile @@ -40,8 +40,8 @@ INCLUDES += $(INCLUDE). \ $(INCLUDE)vendor/base64 \ $(INCLUDE)vendor/bzip2 -HEADERS := $(wildcard src/*.h) $(shell find vendor -name '*.h') -SRCS := $(wildcard src/*.c) $(wildcard src/image/*.c) $(wildcard vendor/bzip2/*.c) vendor/md5/md5.c vendor/sha1/sha1.c vendor/base64/base64.c +HEADERS += $(wildcard src/*.h) $(shell find vendor -name '*.h') +SRCS += $(wildcard src/*.c) $(wildcard src/image/*.c) $(wildcard vendor/bzip2/*.c) vendor/md5/md5.c vendor/sha1/sha1.c vendor/base64/base64.c DESKTOP_BACKEND := glfw3 AUDIO_BACKEND := miniaudio @@ -208,7 +208,7 @@ endif build/butterscotch: $(OBJS) @{ [ -z "$(NO_COLOR)" ] && [ -t 1 ]; } && printf " \033[1;34mLD\033[0m butterscotch\n" || printf " LD butterscotch\n" - $(V)$(_CC) $(LDFLAGS) $(OBJS) $(LIBS) $(EXTRALIBS) $(OUTPUT_EXE)$@ + $(V)$(CCLINK) $(LDFLAGS) $(OBJS) $(LIBS) $(EXTRALIBS) $(OUTPUT_EXE)$@ @[ -f $@.exe ] && chmod +x $@.exe || true build/%.c.$(OBJ_EXT): %.c compat/config.mk $(if $(DISABLE_MMD),$(HEADERS)) diff --git a/compat/configure.sh b/compat/configure.sh index ea7dea7e7..a03a556f3 100644 --- a/compat/configure.sh +++ b/compat/configure.sh @@ -109,7 +109,6 @@ if $CC /nologo tmp/test.c /Fetmp/a.out >> tmp/config.log 2>&1; then config "OUTPUT_EXE := $output_exe" config 'MSVC := 1' config 'OBJ_EXT := obj' - config "_CC := \$(CC) /nologo" config 'CFLAGS := /O2 /DNDEBUG' config 'INCLUDE := /I' config 'DEFINE := /D' @@ -123,7 +122,6 @@ elif $CC tmp/test.c -o tmp/a.out >> tmp/config.log 2>&1; then config "OUTPUT_OBJ := -o\$(space)" config "OUTPUT_EXE := -o\$(space)" config 'OBJ_EXT := o' - config "_CC := \$(CC)" config 'CFLAGS := -O2 -DNDEBUG' config 'INCLUDE := -I' config 'DEFINE := -D' @@ -154,6 +152,27 @@ if [ -n "$target" ]; then fi fi +printf '%s' "\ +int main(void){ + int a = 0; + ++a; + int b = a; + return b; +} +" > tmp/test.c + +if ! nolink=1 check 'if C supports mixed declarations and code'; then + if [ "$syntax" = 'msvc' ]; then + config "CCLINK := $CC" + CC="$CC /TP" + else + printf 'Support for mixed declarations and code is required, maybe try building in C++ mode.\n' + exit 1 + fi +fi + +config "_CC := $CC" + configlog 'checking the target OS' if checkdefine '_WIN32' > /dev/null; then printgreen 'windows' @@ -215,6 +234,7 @@ int main(void){return 0;} if ! nolink=1 check 'if stdbool.h works'; then # Needed for GCC 2.95, where stdbool.h doesn't work in C++ mode include 'compat/stdbool' + config 'HEADERS += compat/stdbool/stdbool.h' fi printf '%s' "\ @@ -224,6 +244,7 @@ int main(void){return 0;} if ! nolink=1 check 'if stdint.h works'; then include 'compat/stdint' + config 'HEADERS += compat/stdint/stdint.h' printf '%s' "\ #include int main(void){return 0;} @@ -369,6 +390,7 @@ int main(int argc,char *argv[]){ if ! check 'for getopt_long'; then include 'compat/getopt' + config 'HEADERS += compat/getopt/getopt.h' fi printf '%s' "\ @@ -383,6 +405,7 @@ if ! check 'for snprintf'; then include 'compat/stdio' define 'NO_SNPRINTF' config 'SRCS += compat/stdio/printf.c' + config 'HEADERS += compat/stdio/printf.h' fi rm -f tmp/test.c tmp/a.out test.obj From 433207e2c4c30ae3b30d6ffce34563d0c042d9ab Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 16:05:58 -0400 Subject: [PATCH 50/57] remove build.cmd and configure.cmd --- build.cmd | 139 ------------------------------------------- compat/configure.cmd | 110 ---------------------------------- 2 files changed, 249 deletions(-) delete mode 100644 build.cmd delete mode 100644 compat/configure.cmd diff --git a/build.cmd b/build.cmd deleted file mode 100644 index c3c95b8d1..000000000 --- a/build.cmd +++ /dev/null @@ -1,139 +0,0 @@ -@echo off -setlocal enabledelayedexpansion - -if "%1"=="clean" ( - if exist build rmdir /s /q build - exit /b 0 -) - -if "%CC%"=="" set CC=cl -if "%CFLAGS%"=="" set CFLAGS=/O2 /DNDEBUG /nologo - -if not exist compat\tmp mkdir compat\tmp ->compat\tmp\cc-new echo.%CC% -if not exist compat\config.mk goto run_config -if not exist compat\tmp\cc goto run_config -fc compat\tmp\cc compat\tmp\cc-new >nul 2>&1 -if errorlevel 1 goto run_config -goto skip_config -:run_config -call compat\configure.cmd -copy /y compat\tmp\cc-new compat\tmp\cc >nul -:skip_config -del compat\tmp\cc-new 2>nul - -if not defined DESKTOP_BACKEND set DESKTOP_BACKEND=glfw3 -if not defined AUDIO_BACKEND set AUDIO_BACKEND=miniaudio - -if defined DISABLE_LEGACY_GL if defined DISABLE_MODERN_GL ( - echo must enable at least 1 renderer - exit /b 1 -) - -if defined DISABLE_WAD14 if defined DISABLE_WAD16 if defined DISABLE_WAD17 ( - echo must enable at least 1 bytecode version - exit /b 1 -) - -set DEFINES= -set DEFINES=%DEFINES% /DENABLE_VM_GML_PROFILER -set DEFINES=%DEFINES% /DENABLE_VM_OPCODE_PROFILER -set DEFINES=%DEFINES% /DENABLE_VM_STUB_LOGS -set DEFINES=%DEFINES% /DENABLE_VM_TRACING -set DEFINES=%DEFINES% /DBUTTERSCOTCH_COMMIT_DATE=\"unknown\" -set DEFINES=%DEFINES% /DBUTTERSCOTCH_COMMIT_HASH=\"unknown\" -set DEFINES=%DEFINES% /D_CRT_SECURE_NO_WARNINGS -set DEFINES=%DEFINES% /DWIN32_LEAN_AND_MEAN -set DEFINES=%DEFINES% /DNO_STRTOK_R - -if not defined DISABLE_WAD14 set DEFINES=%DEFINES% /DENABLE_WAD14 -if not defined DISABLE_WAD16 set DEFINES=%DEFINES% /DENABLE_WAD16 -if not defined DISABLE_WAD17 set DEFINES=%DEFINES% /DENABLE_WAD17 -if not defined DISABLE_LEGACY_GL set DEFINES=%DEFINES% /DENABLE_LEGACY_GL -if not defined DISABLE_MODERN_GL set DEFINES=%DEFINES% /DENABLE_MODERN_GL - -if "%DESKTOP_BACKEND%"=="glfw3" set DEFINES=%DEFINES% /DUSE_GLFW3 -if "%DESKTOP_BACKEND%"=="glfw2" set DEFINES=%DEFINES% /DUSE_GLFW2 -if "%DESKTOP_BACKEND%"=="sdl1" set DEFINES=%DEFINES% /DUSE_SDL1 -if "%DESKTOP_BACKEND%"=="sdl2" set DEFINES=%DEFINES% /DUSE_SDL2 -if "%DESKTOP_BACKEND%"=="sdl3" set DEFINES=%DEFINES% /DUSE_SDL3 -if "%AUDIO_BACKEND%"=="miniaudio" set DEFINES=%DEFINES% /DUSE_MINIAUDIO -if "%AUDIO_BACKEND%"=="openal" set DEFINES=%DEFINES% /DUSE_OPENAL - -set INCLUDES= -set INCLUDES=%INCLUDES% /I. -set INCLUDES=%INCLUDES% /Isrc -set INCLUDES=%INCLUDES% /Ivendor/stb/ds -set INCLUDES=%INCLUDES% /Isrc/image -set INCLUDES=%INCLUDES% /Ivendor/stb/image -set INCLUDES=%INCLUDES% /Ivendor/stb/vorbis -set INCLUDES=%INCLUDES% /Ivendor/md5 -set INCLUDES=%INCLUDES% /Ivendor/sha1 -set INCLUDES=%INCLUDES% /Ivendor/base64 -set INCLUDES=%INCLUDES% /Ivendor/bzip2 -set INCLUDES=%INCLUDES% /Isrc/desktop -set INCLUDES=%INCLUDES% /Icompat/getopt -set INCLUDES=%INCLUDES% /Ivendor/glad/include -if not defined DISABLE_LEGACY_GL set INCLUDES=%INCLUDES% /Isrc/gl_common /Isrc/gl_legacy /Isrc/gl -if not defined DISABLE_MODERN_GL ( - if defined DISABLE_LEGACY_GL set INCLUDES=%INCLUDES% /Isrc/gl_common - set INCLUDES=%INCLUDES% /Isrc/gl -) -if "%AUDIO_BACKEND%"=="miniaudio" set INCLUDES=%INCLUDES% /Isrc/audio/miniaudio /Ivendor/miniaudio -if "%AUDIO_BACKEND%"=="openal" set INCLUDES=%INCLUDES% /Isrc/audio/openal - -set LIBS=winmm.lib -if "%DESKTOP_BACKEND%"=="glfw3" ( - if defined GLFW3_LIBS ( set LIBS=%LIBS% %GLFW3_LIBS% - ) else set LIBS=%LIBS% glfw3.lib opengl32.lib gdi32.lib -) else if "%DESKTOP_BACKEND%"=="glfw2" ( - if defined GLFW2_LIBS ( set LIBS=%LIBS% %GLFW2_LIBS% - ) else set LIBS=%LIBS% glfw.lib opengl32.lib gdi32.lib -) else if "%DESKTOP_BACKEND%"=="sdl1" ( - if defined SDL1_LIBS ( set LIBS=%LIBS% %SDL1_LIBS% - ) else set LIBS=%LIBS% sdl.lib -) else if "%DESKTOP_BACKEND%"=="sdl2" ( - if defined SDL2_LIBS ( set LIBS=%LIBS% %SDL2_LIBS% - ) else set LIBS=%LIBS% sdl2.lib -) else if "%DESKTOP_BACKEND%"=="sdl3" ( - if defined SDL3_LIBS ( set LIBS=%LIBS% %SDL3_LIBS% - ) else set LIBS=%LIBS% sdl3.lib -) - -set SRCS= -set SRCS=%SRCS% src\*.c -set SRCS=%SRCS% src\image\*.c -set SRCS=%SRCS% src\desktop\*.c -set SRCS=%SRCS% "src\desktop\backends\%DESKTOP_BACKEND%.c" -set SRCS=%SRCS% vendor\bzip2\*.c -set SRCS=%SRCS% vendor\md5\*.c -set SRCS=%SRCS% vendor\sha1\*.c -set SRCS=%SRCS% vendor\base64\*.c -set SRCS=%SRCS% vendor\glad\src\glad.c -if not defined DISABLE_LEGACY_GL set SRCS=%SRCS% src\gl_common\*.c src\gl_legacy\*.c -if not defined DISABLE_MODERN_GL ( - if defined DISABLE_LEGACY_GL set SRCS=%SRCS% src\gl_common\*.c - set SRCS=%SRCS% src\gl\*.c -) -if "%AUDIO_BACKEND%"=="miniaudio" set SRCS=%SRCS% src\audio\miniaudio\*.c -if "%AUDIO_BACKEND%"=="openal" set SRCS=%SRCS% src\audio\openal\*.c - -if exist compat\config.bat call compat\config.bat - -if not defined CC_COMPILE set CC_COMPILE=%CC% - -if not exist build mkdir build - -set OBJS= -for %%f in (%SRCS%) do ( - set "p=%%~dpf" - set "n=%%~nf" - set "p=!p:%CD%\=!" - if not exist "build\!p!" mkdir "build\!p!" - %CC_COMPILE% %CFLAGS% %DEFINES% %INCLUDES% /c "%%f" /Fo"build\!p!!n!.obj" - if errorlevel 1 exit /b 1 - set OBJS=!OBJS! "build\!p!!n!.obj" -) - -%CC% %CFLAGS% !OBJS! /Febuild\butterscotch.exe /link %LIBS% %EXTRALIBS% -if errorlevel 1 exit /b 1 diff --git a/compat/configure.cmd b/compat/configure.cmd deleted file mode 100644 index 895e1ce63..000000000 --- a/compat/configure.cmd +++ /dev/null @@ -1,110 +0,0 @@ -@echo off -setlocal enabledelayedexpansion - -if "%CC%"=="" ( - echo Don't run this directly - exit /b 1 -) - -cd /d "%~dp0" - -if not exist tmp mkdir tmp - -type nul > tmp\config.log -> config.bat echo @echo off - ->tmp\test.c echo.#include ^ ->>tmp\test.c echo.int main(void^){return 0;} -call :check if stdbool.h works -if errorlevel 1 ( - >>config.bat echo set INCLUDES=%%INCLUDES%% /Icompat\stdbool -) - ->tmp\test.c echo.#include ^ ->>tmp\test.c echo.int main(void^){return 0;} -call :check if stdint.h works -if errorlevel 1 ( - >>config.bat echo set INCLUDES=%%INCLUDES%% /Icompat\stdint -) - ->tmp\test.c echo.#include ^ ->>tmp\test.c echo.int main(void^){ ->>tmp\test.c echo. char buf[8]; ->>tmp\test.c echo. return snprintf(buf, sizeof(buf^), "test"^); ->>tmp\test.c echo.} -call :check for snprintf -if errorlevel 1 ( - >>config.bat echo set DEFINES=%%DEFINES%% /DNO_SNPRINTF - >>config.bat echo set INCLUDES=%%INCLUDES%% /Icompat\stdio - >>config.bat echo set SRCS=%%SRCS%% compat\stdio\printf.c -) - ->tmp\test.c echo.#include ^ ->>tmp\test.c echo.int main(void^){return fmin(0,0);} -call :check for fmin -if errorlevel 1 ( - >>config.bat echo set DEFINES=%%DEFINES%% /DNO_FMIN -) - ->tmp\test.c echo.#include ^ ->>tmp\test.c echo.int main(void^){return fmax(0,0);} -call :check for fmax -if errorlevel 1 ( - >>config.bat echo set DEFINES=%%DEFINES%% /DNO_FMAX -) - ->tmp\test.c echo.#include ^ ->>tmp\test.c echo.int main(void^){return round(0);} -call :check for round -if errorlevel 1 ( - >>config.bat echo set DEFINES=%%DEFINES%% /DNO_ROUND -) - ->tmp\test.c echo.#include ^ ->>tmp\test.c echo.int main(void^){return roundf(0);} -call :check for roundf -if errorlevel 1 ( - >>config.bat echo set DEFINES=%%DEFINES%% /DNO_ROUNDF -) - ->tmp\test.c echo.#include ^ ->>tmp\test.c echo.int main(void^){return log2(1);} -call :check for log2 -if errorlevel 1 ( - >>config.bat echo set DEFINES=%%DEFINES%% /DNO_LOG2 -) - ->tmp\test.c echo.#include ^ ->>tmp\test.c echo.int main(void^){ ->>tmp\test.c echo. puts(__func__); ->>tmp\test.c echo. return 0; ->>tmp\test.c echo.} -call :check if __func__ works -if errorlevel 1 ( - >>config.bat echo set DEFINES=%%DEFINES%% /D__func__=\"unknown\" -) - ->tmp\test.c echo.int main(void^){ ->>tmp\test.c echo. int a = 1; ->>tmp\test.c echo. ++a; ->>tmp\test.c echo. int b = a; ->>tmp\test.c echo. return b; ->>tmp\test.c echo.} -call :check if C supports mixed declarations and code -if errorlevel 1 ( - >>config.bat echo set CC_COMPILE=%%CC%% /TP -) - -del tmp\test.c tmp\test.obj 2>nul -exit /b 0 - -:check -echo.checking %* >> tmp\config.log -%CC% /nologo /Oi- tmp\test.c /c /Fo:tmp\test.obj >> tmp\config.log 2>&1 -if %errorlevel% equ 0 ( - echo checking %*: yes - exit /b 0 -) else ( - echo checking %*: no - exit /b 1 -) From 44c57a72002abfbeb3a8bc4dd1961859500845f2 Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 16:16:51 -0400 Subject: [PATCH 51/57] fix --- compat/configure.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compat/configure.sh b/compat/configure.sh index a03a556f3..2397c6ae0 100644 --- a/compat/configure.sh +++ b/compat/configure.sh @@ -161,9 +161,9 @@ int main(void){ } " > tmp/test.c +config "CCLINK := $CC" if ! nolink=1 check 'if C supports mixed declarations and code'; then if [ "$syntax" = 'msvc' ]; then - config "CCLINK := $CC" CC="$CC /TP" else printf 'Support for mixed declarations and code is required, maybe try building in C++ mode.\n' From 35f7ba624e6b580c2f9dd76ccff51c572000a895 Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 16:43:03 -0400 Subject: [PATCH 52/57] fix --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index d81ac68ee..781574083 100644 --- a/Makefile +++ b/Makefile @@ -208,7 +208,7 @@ endif build/butterscotch: $(OBJS) @{ [ -z "$(NO_COLOR)" ] && [ -t 1 ]; } && printf " \033[1;34mLD\033[0m butterscotch\n" || printf " LD butterscotch\n" - $(V)$(CCLINK) $(LDFLAGS) $(OBJS) $(LIBS) $(EXTRALIBS) $(OUTPUT_EXE)$@ + $(V)MSYS2_ARG_CONV_EXCL='*' $(CCLINK) $(LDFLAGS) $(OBJS) $(LIBS) $(EXTRALIBS) $(OUTPUT_EXE)$@ @[ -f $@.exe ] && chmod +x $@.exe || true build/%.c.$(OBJ_EXT): %.c compat/config.mk $(if $(DISABLE_MMD),$(HEADERS)) From 116cd870a85735093e3006c8f1e4eeb2fcefdd9d Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 17:19:35 -0400 Subject: [PATCH 53/57] isinf and isnan proper tests --- compat/configure.sh | 18 ++++++++++++++++++ src/real_type.h | 9 +++++---- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/compat/configure.sh b/compat/configure.sh index 2397c6ae0..4aa69fdfc 100644 --- a/compat/configure.sh +++ b/compat/configure.sh @@ -365,6 +365,24 @@ if ! check 'for roundf' $lm; then define 'NO_ROUNDF' fi +printf '%s' "\ +#include +int main(void){return isinf(0.0);} +" > tmp/test.c + +if ! check 'for isinf' $lm; then + define 'NO_ISINF' +fi + +printf '%s' "\ +#include +int main(void){return isnan(0.0);} +" > tmp/test.c + +if ! check 'for isnan' $lm; then + define 'NO_ISNAN' +fi + printf '%s' "\ #include int main(void){ diff --git a/src/real_type.h b/src/real_type.h index 9b9e1ba37..dc7d0c4a4 100644 --- a/src/real_type.h +++ b/src/real_type.h @@ -6,10 +6,11 @@ #include #include -#if defined(_MSC_VER) && _MSC_VER < 1800 -#include -#define isinf(x) (!_finite(x) && !_isnan(x)) -#define isnan(x) _isnan(x) +#ifdef NO_ISNAN +#define isnan(x) (x != x) +#endif +#ifdef NO_ISINF +#define isinf(x) ((x) == INFINITY || (x) == -INFINITY) #endif #ifdef USE_FLOAT_REALS From 0b8e28485a5203eefb5fbd9c4334c9f54483cdfc Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Wed, 22 Jul 2026 17:22:58 -0400 Subject: [PATCH 54/57] fix --- src/math_compat.h | 4 ---- src/real_type.h | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/math_compat.h b/src/math_compat.h index 3aff983e8..7d6f52246 100644 --- a/src/math_compat.h +++ b/src/math_compat.h @@ -106,10 +106,6 @@ static float roundf(float x) { #endif -#ifndef INFINITY -#define INFINITY ((float)1e39) -#endif - #ifndef M_PI #define M_PI 3.14159265358979323846 #endif diff --git a/src/real_type.h b/src/real_type.h index dc7d0c4a4..aeaff8a3f 100644 --- a/src/real_type.h +++ b/src/real_type.h @@ -6,6 +6,10 @@ #include #include +#ifndef INFINITY +#define INFINITY ((float)1e39) +#endif + #ifdef NO_ISNAN #define isnan(x) (x != x) #endif From d72e09f90e45b0b30635113e503a7884012e0ebd Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Thu, 23 Jul 2026 08:24:19 -0400 Subject: [PATCH 55/57] msvc 8 maybe --- src/runner.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/runner.h b/src/runner.h index 78bbfaa87..0c51eb1d6 100644 --- a/src/runner.h +++ b/src/runner.h @@ -425,6 +425,8 @@ typedef struct { FlattenedCollisionEvent* events; } FlattenedCollisionEventList; +typedef struct { char* key; int value; } DisabledObjEntry; + struct Runner { DataWin* dataWin; VMContext* vmContext; @@ -517,7 +519,7 @@ struct Runner { int32_t viewportY; // Y offset in window (letterboxing) int32_t viewportW; // Scaled game width in window int32_t viewportH; // Scaled game height in window - struct { char* key; int value; }* disabledObjects; // stb_ds string hashmap, nullptr = no filtering + DisabledObjEntry* disabledObjects; // stb_ds string hashmap, nullptr = no filtering struct { int key; Instance* value; }* instancesById; bool forceDrawDepth; bool applyOffsetForPrimitives; From 8502773f9f0cf00a13d425301e3dc20e0b0e745a Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Thu, 23 Jul 2026 08:42:56 -0400 Subject: [PATCH 56/57] msvc 8 --- Makefile | 2 +- src/runner.h | 3 ++- src/vm.h | 10 +++++++--- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 781574083..c749f82c7 100644 --- a/Makefile +++ b/Makefile @@ -178,7 +178,7 @@ LIBS += -static LIBS += -lwinmm else LIBS += winmm.lib -DEFINES += $(DEFINE)_CRT_SECURE_NO_WARNINGS +DEFINES += $(DEFINE)_CRT_SECURE_NO_WARNINGS $(DEFINE)_CRT_SECURE_NO_DEPRECATE endif DEFINES += $(DEFINE)WIN32_LEAN_AND_MEAN else diff --git a/src/runner.h b/src/runner.h index 0c51eb1d6..066a7cadb 100644 --- a/src/runner.h +++ b/src/runner.h @@ -425,6 +425,7 @@ typedef struct { FlattenedCollisionEvent* events; } FlattenedCollisionEventList; +typedef struct { char* key; int32_t value; } AssetsByNameEntry; typedef struct { char* key; int value; } DisabledObjEntry; struct Runner { @@ -449,7 +450,7 @@ struct Runner { // Precomputed per-object and per-slot CSR tables of resolved event handlers. Replaces the per-dispatch parent-chain walk in findEventCodeIdAndOwner. ResolvedEventTable eventTable; // Precomputed assets map. - struct { char* key; int32_t value; }* assetsByName; + AssetsByNameEntry* assetsByName; // For each event type, the deduplicated list of object indices that respond to ANY subtype of that event (including via inheritance). Derived from the event table; used by collision dispatch to skip non-collision objects in the outer loop. // Length = OBJT_EVENT_TYPE_COUNT. int32_t** objectsWithAnyEventOfType; diff --git a/src/vm.h b/src/vm.h index 617280b5c..875414677 100644 --- a/src/vm.h +++ b/src/vm.h @@ -186,6 +186,10 @@ typedef struct { char* message; } VMException; +typedef struct { char* key; int32_t value; } CodeIndexByNameEntry; +typedef struct { char* key; CodeLocals* value; } CodeLocalsMapEntry; +typedef struct { int32_t key; int32_t* value; } CrossRefMapEntry; + // ===[ VMContext - Holds all VM state ]=== // Fields are ordered by access frequency so that the hottest data sits in the first bytes of the struct // This way data can be kept "hot" in the CPU cache or, depending on the platform, in scratchpad RAM @@ -245,9 +249,9 @@ struct VMContext { BuiltinEntry* builtinMap; bool registeredBuiltinFunctions; // funcName -> codeIndex hash map (stb_ds) - struct { char* key; int32_t value; }* codeIndexByName; + CodeIndexByNameEntry* codeIndexByName; // codeName -> CodeLocals* hash map (stb_ds) - struct { char* key; CodeLocals* value; }* codeLocalsMap; + CodeLocalsMapEntry* codeLocalsMap; // BC13/BC14/BC17+: A map of CODE indexes -> localVars slot lookup map IntIntHashMap* codeLocalsSlotMaps; // varName -> varID hash map for self/instance-scoped variables (stb_ds). @@ -258,7 +262,7 @@ struct VMContext { // "codeName\tfuncName" -> true, for deduplicating stubbed function warnings StringBooleanEntry* loggedStubbedFuncs; // Cross-reference map for disassembler: targetCodeIndex -> stb_ds array of callerCodeIndex - struct { int32_t key; int32_t* value; }* crossRefMap; + CrossRefMapEntry* crossRefMap; bool alwaysLogUnknownFunctions; bool alwaysLogStubbedFunctions; #ifdef ENABLE_VM_TRACING From 1d9543c37f0a21d3b8c9bf787551f3e1d3901327 Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Thu, 23 Jul 2026 08:43:07 -0400 Subject: [PATCH 57/57] update --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b8ed73571..467388c30 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ The following compilers have been tested to successfully build butterscotch, old * GCC 2.7 and up in C++ mode, and 3.0 and up in C99 mode * Clang 1.1 and up * TinyCC 0.9.27 and up -* MSVC 9.0 and up +* MSVC 8.0 and up ## Community Ports