diff --git a/.github/workflows/presubmit.yml b/.github/workflows/presubmit.yml index 207a050..a278e61 100644 --- a/.github/workflows/presubmit.yml +++ b/.github/workflows/presubmit.yml @@ -109,7 +109,7 @@ jobs: shell: cmd run: | call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars${{ matrix.architecture }}.bat" - python.exe build.py --cfg ${{ matrix.configuration }} --paland -v + python.exe build.py --cfg ${{ matrix.configuration }} --paland -v --build-${{ matrix.architecture }}-bit size-reports: runs-on: ubuntu-latest @@ -127,49 +127,82 @@ jobs: shell: bash run: | . /work/venv/bin/activate - echo Minimal: - arm-none-eabi-gcc -mcpu=cortex-m0 -mthumb -Os -x c -c -o cm0-min.o -DNANOPRINTF_IMPLEMENTATION -DNANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=0 - <<< '#include "nanoprintf.h"' - arm-none-eabi-nm --print-size --size-sort cm0-min.o | python tests/size_report.py + arm-none-eabi-gcc -DNANOPRINTF_SIZE_REPORT=0 -mcpu=cortex-m0 -Os -c -o cm0-0.o tests/size_report.c + arm-none-eabi-nm --print-size --size-sort cm0-0.o | python tests/size_report.py + + echo Binary: + arm-none-eabi-gcc -DNANOPRINTF_SIZE_REPORT=1 -mcpu=cortex-m0 -Os -c -o cm0-1.o tests/size_report.c + arm-none-eabi-nm --print-size --size-sort cm0-1.o | python tests/size_report.py + + echo Field Width + Precision: + arm-none-eabi-gcc -DNANOPRINTF_SIZE_REPORT=2 -mcpu=cortex-m0 -Os -c -o cm0-2.o tests/size_report.c + arm-none-eabi-nm --print-size --size-sort cm0-2.o | python tests/size_report.py - echo Medium: - arm-none-eabi-gcc -mcpu=cortex-m0 -mthumb -Os -x c -c -o cm0-med.o -DNANOPRINTF_IMPLEMENTATION -DNANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=0 - <<< '#include "nanoprintf.h"' - arm-none-eabi-nm --print-size --size-sort cm0-med.o | python tests/size_report.py + echo Field Width + Precision + Binary: + arm-none-eabi-gcc -DNANOPRINTF_SIZE_REPORT=3 -mcpu=cortex-m0 -Os -c -o cm0-3.o tests/size_report.c + arm-none-eabi-nm --print-size --size-sort cm0-3.o | python tests/size_report.py - echo Maximal: - arm-none-eabi-gcc -mcpu=cortex-m0 -mthumb -Os -x c -c -o cm0-max.o -DNANOPRINTF_IMPLEMENTATION -DNANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=1 - <<< '#include "nanoprintf.h"' - arm-none-eabi-nm --print-size --size-sort cm0-max.o | python tests/size_report.py + echo Float: + arm-none-eabi-gcc -DNANOPRINTF_SIZE_REPORT=4 -mcpu=cortex-m0 -Os -c -o cm0-4.o tests/size_report.c + arm-none-eabi-nm --print-size --size-sort cm0-4.o | python tests/size_report.py + + echo Everything: + arm-none-eabi-gcc -DNANOPRINTF_SIZE_REPORT=5 -mcpu=cortex-m0 -Os -c -o cm0-5.o tests/size_report.c + arm-none-eabi-nm --print-size --size-sort cm0-5.o | python tests/size_report.py - name: Cortex-M4 shell: bash run: | . /work/venv/bin/activate - echo Minimal: - arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -Os -x c -c -o cm4-min.o -DNANOPRINTF_IMPLEMENTATION -DNANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=0 - <<< '#include "nanoprintf.h"' - arm-none-eabi-nm --print-size --size-sort cm4-min.o | python tests/size_report.py + arm-none-eabi-gcc -DNANOPRINTF_SIZE_REPORT=0 -mcpu=cortex-m4 -Os -c -o cm4-0.o tests/size_report.c + arm-none-eabi-nm --print-size --size-sort cm4-0.o | python tests/size_report.py + + echo Binary: + arm-none-eabi-gcc -DNANOPRINTF_SIZE_REPORT=1 -mcpu=cortex-m4 -Os -c -o cm4-1.o tests/size_report.c + arm-none-eabi-nm --print-size --size-sort cm4-1.o | python tests/size_report.py - echo Medium: - arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -Os -x c -c -o cm4-med.o -DNANOPRINTF_IMPLEMENTATION -DNANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=0 - <<< '#include "nanoprintf.h"' - arm-none-eabi-nm --print-size --size-sort cm4-med.o | python tests/size_report.py + echo Field Width + Precision: + arm-none-eabi-gcc -DNANOPRINTF_SIZE_REPORT=2 -mcpu=cortex-m4 -Os -c -o cm4-2.o tests/size_report.c + arm-none-eabi-nm --print-size --size-sort cm4-2.o | python tests/size_report.py - echo Maximal: - arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -Os -x c -c -o cm4-max.o -DNANOPRINTF_IMPLEMENTATION -DNANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=1 - <<< '#include "nanoprintf.h"' - arm-none-eabi-nm --print-size --size-sort cm4-max.o | python tests/size_report.py + echo Field Width + Precision + Binary: + arm-none-eabi-gcc -DNANOPRINTF_SIZE_REPORT=3 -mcpu=cortex-m4 -Os -c -o cm4-3.o tests/size_report.c + arm-none-eabi-nm --print-size --size-sort cm4-3.o | python tests/size_report.py - - name: x64 + echo Float: + arm-none-eabi-gcc -DNANOPRINTF_SIZE_REPORT=4 -mcpu=cortex-m4 -Os -c -o cm4-4.o tests/size_report.c + arm-none-eabi-nm --print-size --size-sort cm4-4.o | python tests/size_report.py + + echo Everything: + arm-none-eabi-gcc -DNANOPRINTF_SIZE_REPORT=5 -mcpu=cortex-m4 -Os -c -o cm4-5.o tests/size_report.c + arm-none-eabi-nm --print-size --size-sort cm4-5.o | python tests/size_report.py + + - name: Linux x64 shell: bash run: | . /work/venv/bin/activate - echo Minimal: - gcc -Os -x c -c -o x64-min.o -DNANOPRINTF_IMPLEMENTATION -DNANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=0 - <<< '#include "nanoprintf.h"' - nm --print-size --size-sort x64-min.o | python tests/size_report.py + gcc -DNANOPRINTF_SIZE_REPORT=0 -Os -c -o x64-0.o tests/size_report.c + nm --print-size --size-sort x64-0.o | python tests/size_report.py + + echo Binary: + gcc -DNANOPRINTF_SIZE_REPORT=1 -Os -c -o x64-1.o tests/size_report.c + nm --print-size --size-sort x64-1.o | python tests/size_report.py + + echo Field Width + Precision: + gcc -DNANOPRINTF_SIZE_REPORT=2 -Os -c -o x64-2.o tests/size_report.c + nm --print-size --size-sort x64-2.o | python tests/size_report.py + + echo Field Width + Precision + Binary: + gcc -DNANOPRINTF_SIZE_REPORT=3 -Os -c -o x64-3.o tests/size_report.c + nm --print-size --size-sort x64-3.o | python tests/size_report.py - echo Medium: - gcc -Os -x c -c -o x64-med.o -DNANOPRINTF_IMPLEMENTATION -DNANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=0 - <<< '#include "nanoprintf.h"' - nm --print-size --size-sort x64-med.o | python tests/size_report.py + echo Float: + gcc -DNANOPRINTF_SIZE_REPORT=4 -Os -c -o x64-4.o tests/size_report.c + nm --print-size --size-sort x64-4.o | python tests/size_report.py - echo Maximal: - gcc -Os -x c -c -o x64-max.o -DNANOPRINTF_IMPLEMENTATION -DNANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=1 - <<< '#include "nanoprintf.h"' - nm --print-size --size-sort x64-max.o | python tests/size_report.py + echo Everything: + gcc -DNANOPRINTF_SIZE_REPORT=5 -Os -c -o x64-5.o tests/size_report.c + nm --print-size --size-sort x64-5.o | python tests/size_report.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 3475341..2cf1206 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,64 +92,72 @@ foreach(fw 0 1) foreach(precision 0 1) foreach(large 0 1) foreach(float 0 1) - foreach(wb 0 1) - if ((precision EQUAL 0) AND (float EQUAL 1)) - continue() - endif() - - set(test_name "") - if (fw EQUAL 1) - string(APPEND test_name "_fieldwidth") - endif() - if (precision EQUAL 1) - string(APPEND test_name "_precision") - endif() - if (large EQUAL 1) - string(APPEND test_name "_large") - endif() - if (float EQUAL 1) - string(APPEND test_name "_float") - endif() - if (wb EQUAL 1) - string(APPEND test_name "_writeback") - endif() - - # Run a simple compilation test - set(compilation_test_name "npf_compile${test_name}_c") - npf_compilation_c_test(${compilation_test_name}) - target_compile_definitions( - ${compilation_test_name} - PRIVATE - NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=${fw} - NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=${precision} - NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=${large} - NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=${float} - NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=${wb}) - - # Run conformance tests (c++) - set(conformance_test_name "npf_conform${test_name}") - npf_test(${conformance_test_name} tests/conformance.cc) - target_compile_definitions( - ${conformance_test_name} - PRIVATE - NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=${fw} - NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=${precision} - NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=${large} - NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=${float} - NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=${wb}) - - if (NPF_PALAND) - set(paland_test_name "npf_paland${test_name}") - npf_test(${paland_test_name} tests/mpaland-conformance/paland.cc) + foreach(binary 0 1) + foreach(wb 0 1) + if ((precision EQUAL 0) AND (float EQUAL 1)) + continue() + endif() + + set(test_name "") + if (fw EQUAL 1) + string(APPEND test_name "_fieldwidth") + endif() + if (precision EQUAL 1) + string(APPEND test_name "_precision") + endif() + if (large EQUAL 1) + string(APPEND test_name "_large") + endif() + if (float EQUAL 1) + string(APPEND test_name "_float") + endif() + if (binary EQUAL 1) + string(APPEND test_name "_binary") + endif() + if (wb EQUAL 1) + string(APPEND test_name "_writeback") + endif() + + # Run a simple compilation test + set(compilation_test_name "npf_compile${test_name}_c") + npf_compilation_c_test(${compilation_test_name}) target_compile_definitions( - ${paland_test_name} + ${compilation_test_name} PRIVATE NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=${fw} NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=${precision} NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=${large} NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=${float} + NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS=${binary} NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=${wb}) - endif() + + # Run conformance tests (c++) + set(conformance_test_name "npf_conform${test_name}") + npf_test(${conformance_test_name} tests/conformance.cc) + target_compile_definitions( + ${conformance_test_name} + PRIVATE + NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=${fw} + NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=${precision} + NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=${large} + NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=${float} + NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS=${binary} + NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=${wb}) + + if (NPF_PALAND) + set(paland_test_name "npf_paland${test_name}") + npf_test(${paland_test_name} tests/mpaland-conformance/paland.cc) + target_compile_definitions( + ${paland_test_name} + PRIVATE + NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=${fw} + NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=${precision} + NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=${large} + NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=${float} + NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS=${binary} + NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=${wb}) + endif() + endforeach() endforeach() endforeach() endforeach() @@ -181,6 +189,7 @@ add_executable(npf_include_multiple tests/include_multiple.c) set(unit_test_files nanoprintf.h tests/unit_parse_format_spec.cc + tests/unit_binary.cc tests/unit_bufputc.cc tests/unit_ftoa_rev.cc tests/unit_itoa_rev.cc @@ -192,9 +201,21 @@ set(unit_test_files npf_test(unit_tests_normal_sized_formatters "${unit_test_files}") target_compile_definitions(unit_tests_normal_sized_formatters PRIVATE + DOCTEST_CONFIG_SUPER_FAST_ASSERTS NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=0) +if (NPF_32BIT) +target_compile_definitions(unit_tests_normal_sized_formatters + PRIVATE + NANOPRINTF_32_BIT_TESTS) +endif() npf_test(unit_tests_large_sized_formatters "${unit_test_files}") target_compile_definitions(unit_tests_large_sized_formatters PRIVATE + DOCTEST_CONFIG_SUPER_FAST_ASSERTS NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=1) +if (NPF_32BIT) +target_compile_definitions(unit_tests_normal_sized_formatters + PRIVATE + NANOPRINTF_32_BIT_TESTS) +endif() diff --git a/README.md b/README.md index 0d254ec..9d82433 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,13 @@ [![Presubmit Checks](https://github.com/charlesnicholson/nanoprintf/workflows/Presubmit%20Checks/badge.svg)](https://github.com/charlesnicholson/nanoprintf/tree/master/.github/workflows) [![](https://img.shields.io/badge/pylint-10.0-brightgreen.svg)](https://www.pylint.org/) [![](https://img.shields.io/badge/license-public_domain-brightgreen.svg)](https://github.com/charlesnicholson/nanoprintf/blob/master/LICENSE) -nanoprintf is an implementation of snprintf and vsnprintf for embedded systems that, when fully enabled, aims for C11 standard compliance. +nanoprintf is an implementation of snprintf and vsnprintf for embedded systems that, when fully enabled, aim for C11 standard compliance. The primary exceptions are scientific notation (though `%f` is fully supported), and the conversions that require `wcrtomb` to exist. Additionally, C23 binary integer output is optionally supported as per [N2630](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2630.pdf). nanoprintf makes no memory allocations and uses less than 100 bytes of stack. nanoprintf compiles to somewhere between 1-3KB of code on a Cortex-M architecture. nanoprintf is a [single header file](https://github.com/charlesnicholson/nanoprintf/blob/master/nanoprintf.h) in the style of the [stb libraries](https://github.com/nothings/stb). The rest of the repository is tests and scaffolding and not required for use. -nanoprintf is written in a minimal dialect of C99 for maximal compiler compatibility, and compiles cleanly at the highest warning levels on clang, gcc, and msvc in both 32- and 64-bit modes. It's _really_ hard to write portable C89 code, btw, when you don't have any guarantee about what integral type to use to hold a converted pointer representation. +nanoprintf is written in a minimal dialect of C99 for maximal compiler compatibility, and compiles cleanly at the highest warning levels on clang, gcc, and msvc in both 32- and 64-bit modes. nanoprintf does include C standard headers but only uses them for C99 types and argument lists; no calls are made into stdlib / libc, with the exception of any internal double-to-float conversion ABI calls your compiler might emit. As usual, some Windows-specific headers are required if you're compiling natively for msvc. @@ -59,10 +59,11 @@ nanoprintf has the following static configuration flags. * `NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS`: Set to `0` or `1`. Enables precision specifiers. * `NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS`: Set to `0` or `1`. Enables floating-point specifiers. * `NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS`: Set to `0` or `1`. Enables oversized modifiers. +* `NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS`: Set to `0` or `1`. Enables binary specifiers. * `NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS`: Set to `0` or `1`. Enables `%n` for write-back. * `NANOPRINTF_VISIBILITY_STATIC`: Optional define. Marks prototypes as `static` to sandbox nanoprintf. -If no configuration flags are specified, nanoprintf will default to "reasonable" embedded values in an attempt to be helpful: floats enabled, writeback and large formatters disabled. If any configuration flags are explicitly specified, nanoprintf requires that all flags are explicitly specified. +If no configuration flags are specified, nanoprintf will default to "reasonable" embedded values in an attempt to be helpful: floats are enabled, but writeback, binary, and large formatters are disabled. If any configuration flags are explicitly specified, nanoprintf requires that all flags are explicitly specified. If a disabled format specifier feature is used, no conversion will occur and the format specifier string simply will be printed instead. @@ -110,6 +111,7 @@ Like `printf`, `nanoprintf` expects a conversion specification string of the fol * `%p`: Pointers * `%n`: Write the number of bytes written to the pointer vararg * `%f`/`%F`: Floating-point values + * `%b`/`%B`: Binary integers ## Floating Point @@ -117,53 +119,84 @@ Floating point conversion is performed by extracting the value into 64:64 fixed- Despite `nano` in the name, there's no way to do away with double entirely, since the C language standard says that floats are promoted to double any time they're passed into variadic argument lists. nanoprintf casts all doubles back down to floats before doing any conversions. +## Limitations + +No wide-character support exists: the `%lc` and `%ls` fields require that the arg be converted to a char array as if by a call to [wcrtomb](http://man7.org/linux/man-pages/man3/wcrtomb.3.html). When locale and character set conversions get involved, it's hard to keep the name "nano". Accordingly, `%lc` and `%ls` behave like `%c` and `%s`, respectively. + +Currently the only supported float conversions are the decimal forms: `%f` and `%F`. Pull requests welcome! + ## Measurement The CI build is set up to use gcc and nm to measure the compiled size of every pull request. See the [Presubmit Checks](https://github.com/charlesnicholson/nanoprintf/actions/workflows/presubmit.yml) "size reports" job output for recent runs. -Minimal configuration (all features disabled): +The following size measurements are taken against the Cortex-M0 build. + ``` -arm-none-eabi-gcc -mcpu=cortex-m0 -mthumb -Os -x c -c -o cm0-min.o -DNANOPRINTF_IMPLEMENTATION -DNANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=0 - <<< '#include "nanoprintf.h"' -arm-none-eabi-nm --print-size --size-sort cm0-min.o | python tests/size_report.py - +Minimal: +arm-none-eabi-gcc -DNANOPRINTF_SIZE_REPORT=0 -mcpu=cortex-m0 -Os -c -o cm0-0.o tests/size_report.c +arm-none-eabi-nm --print-size --size-sort cm0-0.o | python tests/size_report.py 00000016 00000002 t npf_bufputc_nop 00000000 00000016 t npf_bufputc 0000036e 00000016 T npf_pprintf 000003b8 00000016 T npf_snprintf 00000384 00000034 T npf_vsnprintf 00000018 00000356 T npf_vpprintf +Total size: 0x3ce (974) bytes -Total size: 0x3ce (974 bytes) -``` - -Medium configuration (field width and precision specifiers): -``` -arm-none-eabi-gcc -mcpu=cortex-m0 -mthumb -Os -x c -c -o cm0-med.o -DNANOPRINTF_IMPLEMENTATION -DNANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=0 -DNANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=0 - <<< '#include "nanoprintf.h"' -arm-none-eabi-nm --print-size --size-sort cm0-med.o | python tests/size_report.py - +Binary: +arm-none-eabi-gcc -DNANOPRINTF_SIZE_REPORT=1 -mcpu=cortex-m0 -Os -c -o cm0-1.o tests/size_report.c +arm-none-eabi-nm --print-size --size-sort cm0-1.o | python tests/size_report.py 00000016 00000002 t npf_bufputc_nop 00000000 00000016 t npf_bufputc -0000065e 00000016 T npf_pprintf -000006a8 00000016 T npf_snprintf -00000674 00000034 T npf_vsnprintf -00000018 00000646 T npf_vpprintf - -Total size: 0x6be (1726) bytes -``` - -Maximal configuration (field width, precision, large int, writeback, floating point): -``` -arm-none-eabi-gcc -mcpu=cortex-m0 -mthumb -Os -x c -c -o cm0-max.o -DNANOPRINTF_IMPLEMENTATION -DNANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS=1 -DNANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS=1 - <<< '#include "nanoprintf.h"' -arm-none-eabi-nm --print-size --size-sort cm0-max.o | python tests/size_report.py - +000003c2 00000016 T npf_pprintf +0000040c 00000016 T npf_snprintf +000003d8 00000034 T npf_vsnprintf +00000018 000003aa T npf_vpprintf +Total size: 0x422 (1058) bytes + +Field Width + Precision: +arm-none-eabi-gcc -DNANOPRINTF_SIZE_REPORT=2 -mcpu=cortex-m0 -Os -c -o cm0-2.o tests/size_report.c +arm-none-eabi-nm --print-size --size-sort cm0-2.o | python tests/size_report.py 00000016 00000002 t npf_bufputc_nop 00000000 00000016 t npf_bufputc -00000a2c 00000016 T npf_pprintf -00000a74 00000016 T npf_snprintf -00000a42 00000032 T npf_vsnprintf -00000018 00000a14 T npf_vpprintf - -Total size: 0xa8a (2698) bytes +00000656 00000016 T npf_pprintf +000006a0 00000016 T npf_snprintf +0000066c 00000034 T npf_vsnprintf +00000018 0000063e T npf_vpprintf +Total size: 0x6b6 (1718) bytes + +Field Width + Precision + Binary: +arm-none-eabi-gcc -DNANOPRINTF_SIZE_REPORT=3 -mcpu=cortex-m0 -Os -c -o cm0-3.o tests/size_report.c +arm-none-eabi-nm --print-size --size-sort cm0-3.o | python tests/size_report.py +00000016 00000002 t npf_bufputc_nop +00000000 00000016 t npf_bufputc +000006c6 00000016 T npf_pprintf +00000710 00000016 T npf_snprintf +000006dc 00000034 T npf_vsnprintf +00000018 000006ae T npf_vpprintf +Total size: 0x726 (1830) bytes + +Float: +arm-none-eabi-gcc -DNANOPRINTF_SIZE_REPORT=4 -mcpu=cortex-m0 -Os -c -o cm0-4.o tests/size_report.c +arm-none-eabi-nm --print-size --size-sort cm0-4.o | python tests/size_report.py +00000016 00000002 t npf_bufputc_nop +00000000 00000016 t npf_bufputc +000006fc 00000016 T npf_pprintf +00000744 00000016 T npf_snprintf +00000712 00000032 T npf_vsnprintf +00000018 000006e4 T npf_vpprintf +Total size: 0x75a (1882) bytes + +Everything: +arm-none-eabi-gcc -DNANOPRINTF_SIZE_REPORT=5 -mcpu=cortex-m0 -Os -c -o cm0-5.o tests/size_report.c +arm-none-eabi-nm --print-size --size-sort cm0-5.o | python tests/size_report.py +00000016 00000002 t npf_bufputc_nop +00000000 00000016 t npf_bufputc +00000ab0 00000016 T npf_pprintf +00000af8 00000016 T npf_snprintf +00000ac6 00000032 T npf_vsnprintf +00000018 00000a98 T npf_vpprintf +Total size: 0xb0e (2830) bytes ``` ## Development @@ -183,14 +216,10 @@ The matrix builds [Debug, Release] x [32-bit, 64-bit] x [Mac, Windows, Linux] x One test suite is a fork from the [printf test suite](), which is MIT licensed. It exists as a submodule for licensing purposes- nanoprintf is public domain, so this particular test suite is optional and excluded by default. To build it, retrieve it by updating submodules and add the `--paland` flag to your `./b` invocation. It is not required to use nanoprintf at all. -## Limitations - -No wide-character support exists: the `%lc` and `%ls` fields require that the arg be converted to a char array as if by a call to [wcrtomb](http://man7.org/linux/man-pages/man3/wcrtomb.3.html). When locale and character set conversions get involved, it's hard to keep the name "nano". Accordingly, `%lc` and `%ls` behave like `%c` and `%s`, respectively. - -Currently the only supported float conversions are the decimal forms: `%f` and `%F`. Pull requests welcome! - ## Acknowledgments I implemented Float-to-int conversion using the ideas from [Wojciech Muła](mailto:zdjęcia@garnek.pl)'s [float -> 64:64 fixed algorithm](http://0x80.pl/notesen/2015-12-29-float-to-string.html). I ported the [printf test suite](https://github.com/eyalroz/printf/blob/master/test/test_suite.cpp) to nanoprintf. It was originally from the [mpaland printf project](https://github.com/mpaland/printf) codebase but adopted and improved by [Eyal Rozenberg](https://github.com/eyalroz) and others. (Nanoprintf has many of its own tests, but these are also very thorough and very good!) + +The binary implementation is based on the requirements specified by [Jörg Wunsch](https://github.com/dl8dtl)'s [N2630 proposal](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2630.pdf), hopefully to be accepted into C23! diff --git a/build.py b/build.py index 73ddb0f..baba94b 100644 --- a/build.py +++ b/build.py @@ -32,6 +32,8 @@ def parse_args(): help='CMake configuration') parser.add_argument( '--build-32-bit', help='Compile in 32-bit mode', action='store_true') + parser.add_argument( + '--build-64-bit', help='Compile in 64-bit mode', action='store_true') parser.add_argument( '--paland', help='Compile with Paland\'s printf conformance suite', diff --git a/examples/use_npf_directly/your_project_nanoprintf.cc b/examples/use_npf_directly/your_project_nanoprintf.cc index 525399c..259b42e 100644 --- a/examples/use_npf_directly/your_project_nanoprintf.cc +++ b/examples/use_npf_directly/your_project_nanoprintf.cc @@ -2,6 +2,7 @@ #define NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS 1 #define NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS 1 #define NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS 1 +#define NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS 1 #define NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS 1 // Compile nanoprintf in this translation unit. diff --git a/examples/wrap_npf/your_project_printf.cc b/examples/wrap_npf/your_project_printf.cc index 93ca862..2fbcfa3 100644 --- a/examples/wrap_npf/your_project_printf.cc +++ b/examples/wrap_npf/your_project_printf.cc @@ -4,6 +4,7 @@ #define NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS 1 #define NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS 1 #define NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS 1 +#define NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS 1 #define NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS 0 // Compile nanoprintf in this translation unit. diff --git a/nanoprintf.h b/nanoprintf.h index b3ef57e..0774dc4 100644 --- a/nanoprintf.h +++ b/nanoprintf.h @@ -89,14 +89,16 @@ NPF_VISIBILITY int npf_vpprintf(npf_putc pc, void *pc_ctx, char const *format, // 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_PRECISION_FORMAT_SPECIFIERS) && \ + !defined(NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS) && \ + !defined(NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS) && \ + !defined(NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS) && \ !defined(NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS) #define NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS 1 #define NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS 1 #define NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS 1 #define NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS 0 +#define NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS 0 #define NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS 0 #endif @@ -109,12 +111,16 @@ NPF_VISIBILITY int npf_vpprintf(npf_putc pc, void *pc_ctx, char const *format, #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_FLOAT_FORMAT_SPECIFIERS - #error NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS must be #defined to 0 or 1 +#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 @@ -128,14 +134,16 @@ NPF_VISIBILITY int npf_vpprintf(npf_putc pc, void *pc_ctx, char const *format, #endif // intmax_t / uintmax_t require stdint from c99 / c++11 -#ifndef _MSC_VER - #ifdef __cplusplus - #if __cplusplus < 201103L - #error nanoprintf requires C++11 or later. - #endif - #else - #if __STDC_VERSION__ < 199409L - #error nanoprintf requires C99 or later. +#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 @@ -181,12 +189,13 @@ typedef enum { 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' */ + , NPF_FMT_SPEC_CONV_WRITEBACK /* 'n' */ #endif #if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1 - , - NPF_FMT_SPEC_CONV_FLOAT_DECIMAL /* 'f', 'F' */ + , NPF_FMT_SPEC_CONV_FLOAT_DECIMAL /* 'f', 'F' */ +#endif +#if NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS == 1 + , NPF_FMT_SPEC_CONV_BINARY /* 'b' */ #endif } npf_format_spec_conversion_t; @@ -260,6 +269,10 @@ static int npf_ftoa_rev(char *buf, int *out_frac_chars); #endif +#if NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS == 1 +static int npf_bin_len(npf_uint_t i); +#endif + #if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1 #include #endif @@ -437,6 +450,7 @@ int npf_parse_format_spec(char const *format, npf_format_spec_t *out_spec) { case 'u': out_spec->conv_spec = NPF_FMT_SPEC_CONV_UNSIGNED_INT; break; + #if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1 case 'f': out_spec->conv_spec = NPF_FMT_SPEC_CONV_FLOAT_DECIMAL; @@ -445,22 +459,35 @@ int npf_parse_format_spec(char const *format, npf_format_spec_t *out_spec) { out_spec->conv_spec = NPF_FMT_SPEC_CONV_FLOAT_DECIMAL; out_spec->conv_spec_case = NPF_FMT_SPEC_CONV_CASE_UPPER; break; -#endif +#endif // NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS + #if NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS == 1 case 'n': /* todo: reject string if flags or width or precision exist */ out_spec->conv_spec = NPF_FMT_SPEC_CONV_WRITEBACK; #if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1 out_spec->precision_type = NPF_FMT_SPEC_PRECISION_NONE; -#endif +#endif // NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS break; -#endif +#endif // NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS + case 'p': out_spec->conv_spec = NPF_FMT_SPEC_CONV_POINTER; #if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1 out_spec->precision_type = NPF_FMT_SPEC_PRECISION_NONE; #endif break; + +#if NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS == 1 + case 'b': + out_spec->conv_spec = NPF_FMT_SPEC_CONV_BINARY; + break; + case 'B': + out_spec->conv_spec = NPF_FMT_SPEC_CONV_BINARY; + out_spec->conv_spec_case = NPF_FMT_SPEC_CONV_CASE_UPPER; + break; +#endif + default: return 0; } @@ -474,6 +501,9 @@ int npf_parse_format_spec(char const *format, npf_format_spec_t *out_spec) { case NPF_FMT_SPEC_CONV_OCTAL: case NPF_FMT_SPEC_CONV_HEX_INT: case NPF_FMT_SPEC_CONV_UNSIGNED_INT: +#if NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS == 1 + case NPF_FMT_SPEC_CONV_BINARY: +#endif out_spec->precision = 1; break; #if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1 @@ -657,6 +687,51 @@ int npf_ftoa_rev(char *buf, float f, unsigned base, } #endif +#if NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS == 1 +int npf_bin_len(npf_uint_t u) { + // Return the length of the string representation of 'u', preferring intrinsics. + +#ifdef _MSC_VER // Win64, use _BSR64 for everything. If x86, use _BSR when non-large. + #ifdef _M_X64 + #define NPF_HAVE_BUILTIN_CLZ + unsigned long idx; + _BitScanReverse64(&idx, u); + return u ? (idx + 1) : 1; + #elif NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 0 + #define NPF_HAVE_BUILTIN_CLZ + unsigned long idx; + _BitScanReverse(&idx, u); + return u ? (idx + 1) : 1; + #endif +#else + #if defined(__clang__) || \ + (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 6)))) + #define NPF_HAVE_BUILTIN_CLZ + #endif + + #ifdef NPF_HAVE_BUILTIN_CLZ // modern gcc or any clang + #if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1 + #define NANOPRINTF_CLZ(X) ((sizeof(long long) * 8) - (size_t)__builtin_clzll(X)) + #else + #define NANOPRINTF_CLZ(X) ((sizeof(long) * 8) - (size_t)__builtin_clzl(X)) + #endif + return u ? (int)NANOPRINTF_CLZ(u) : 1; + #undef NANOPRINTF_CLZ + #endif +#endif + +#ifndef NPF_HAVE_BUILTIN_CLZ // slow but small software fallback + int n; + for (n = u ? 0 : 1; u; ++n, u >>= 1); + return n; +#endif + +#ifdef NPF_HAVE_BUILTIN_CLZ + #undef NPF_HAVE_BUILTIN_CLZ +#endif +} +#endif + #define NPF_PUTC(VAL) do { pc((VAL), pc_ctx); ++n; } while (0) #define NPF_EXTRACT(MOD, CAST_TO, EXTRACT_AS) \ @@ -684,7 +759,8 @@ int npf_vpprintf(npf_putc pc, void *pc_ctx, char const *format, va_list vlist) { } // Format specifier, convert and write argument - char cbuf_mem[32], *cbuf = cbuf_mem, sign_c; + union { char cbuf_mem[32]; npf_uint_t binval; } u; + char *cbuf = u.cbuf_mem, sign_c; int cbuf_len = 0, need_0x = 0; #if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1 int field_pad = 0; @@ -785,6 +861,9 @@ int npf_vpprintf(npf_putc pc, void *pc_ctx, char const *format, va_list vlist) { { cbuf_len = npf_itoa_rev(cbuf, val); } } break; +#if NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS == 1 + case NPF_FMT_SPEC_CONV_BINARY: // 'b' +#endif case NPF_FMT_SPEC_CONV_OCTAL: // 'o' case NPF_FMT_SPEC_CONV_HEX_INT: // 'x', 'X' case NPF_FMT_SPEC_CONV_UNSIGNED_INT: { // 'u' @@ -811,15 +890,20 @@ int npf_vpprintf(npf_putc pc, void *pc_ctx, char const *format, va_list vlist) { #if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1 #if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1 - zero = !val; + zero = (val == 0); #endif - if (!val && !fs.precision) { + if ((val == 0) && (fs.precision == 0) && + (fs.precision_type == NPF_FMT_SPEC_PRECISION_LITERAL)) { + // Zero value and explicitly-requested zero precision means "print nothing". if ((fs.conv_spec == NPF_FMT_SPEC_CONV_OCTAL) && fs.alternative_form) { fs.precision = 1; // octal special case, print a single '0' - } else if (fs.precision_type == NPF_FMT_SPEC_PRECISION_LITERAL) { - cbuf_len = 0; // 0 value + 0 precision, print nothing } } 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; (void)base; + } else #endif { cbuf_len = npf_utoa_rev(cbuf, val, base, fs.conv_spec_case); } @@ -828,9 +912,16 @@ int npf_vpprintf(npf_putc pc, void *pc_ctx, char const *format, va_list vlist) { if (fs.conv_spec == NPF_FMT_SPEC_CONV_OCTAL) { cbuf[cbuf_len++] = '0'; } } - // alt form adds '0x' hex but can't write it yet. - if (val && fs.alternative_form && (fs.conv_spec == NPF_FMT_SPEC_CONV_HEX_INT)) { - need_0x = (fs.conv_spec_case == NPF_FMT_SPEC_CONV_CASE_LOWER) ? 'x' : 'X'; + // alt form adds '0x' or '0b' but can't write it yet. + if (val && fs.alternative_form) { + if (fs.conv_spec == NPF_FMT_SPEC_CONV_HEX_INT) { + need_0x = (fs.conv_spec_case == NPF_FMT_SPEC_CONV_CASE_LOWER) ? 'x' : 'X'; + } +#if NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS == 1 + else if (fs.conv_spec == NPF_FMT_SPEC_CONV_BINARY) { + need_0x = (fs.conv_spec_case == NPF_FMT_SPEC_CONV_CASE_LOWER) ? 'b' : 'B'; + } +#endif } } break; @@ -990,8 +1081,13 @@ int npf_vpprintf(npf_putc pc, void *pc_ctx, char const *format, va_list vlist) { } } #endif - // *toa_rev leaves payloads reversed - while (cbuf_len-- > 0) { NPF_PUTC(cbuf[cbuf_len]); } + +#if NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS == 1 + if (fs.conv_spec == NPF_FMT_SPEC_CONV_BINARY) { + while (cbuf_len) { NPF_PUTC('0' + ((u.binval >> (cbuf_len-- - 1)) & 1)); } + } else +#endif + { while (cbuf_len-- > 0) { NPF_PUTC(cbuf[cbuf_len]); } } // payload is reversed #if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1 // real precision comes after the number. diff --git a/tests/include_multiple.c b/tests/include_multiple.c index dac705a..d7b283a 100644 --- a/tests/include_multiple.c +++ b/tests/include_multiple.c @@ -11,6 +11,7 @@ #define NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS 1 #define NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS 1 #define NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS 1 +#define NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS 1 #define NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS 1 #define NANOPRINTF_IMPLEMENTATION diff --git a/tests/mpaland-conformance b/tests/mpaland-conformance index 1ab7eb1..b54b0a9 160000 --- a/tests/mpaland-conformance +++ b/tests/mpaland-conformance @@ -1 +1 @@ -Subproject commit 1ab7eb1a3b282396dff500682aac618d01104cdd +Subproject commit b54b0a935f4d3e2b2f2cec7fa187acbb87374291 diff --git a/tests/size_report.c b/tests/size_report.c new file mode 100644 index 0000000..86940ee --- /dev/null +++ b/tests/size_report.c @@ -0,0 +1,59 @@ +#ifndef NANOPRINTF_SIZE_REPORT + #error NANOPRINTF_SIZE_REPORT must be defined +#endif + +#if NANOPRINTF_SIZE_REPORT == 0 // Minimal + #define NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS 0 + #define NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS 0 + #define NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS 0 + #define NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS 0 + #define NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS 0 + #define NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS 0 + +#elif NANOPRINTF_SIZE_REPORT == 1 // binary + #define NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS 0 + #define NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS 0 + #define NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS 0 + #define NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS 0 + #define NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS 1 + #define NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS 0 + +#elif NANOPRINTF_SIZE_REPORT == 2 // field width + precision + #define NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS 1 + #define NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS 1 + #define NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS 0 + #define NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS 0 + #define NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS 0 + #define NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS 0 + +#elif NANOPRINTF_SIZE_REPORT == 3 // field width + precision + binary + #define NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS 1 + #define NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS 1 + #define NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS 0 + #define NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS 0 + #define NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS 1 + #define NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS 0 + +#elif NANOPRINTF_SIZE_REPORT == 4 // float + #define NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS 0 + #define NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS 1 + #define NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS 1 + #define NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS 0 + #define NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS 0 + #define NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS 0 + +#elif NANOPRINTF_SIZE_REPORT == 5 // everything + #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_BINARY_FORMAT_SPECIFIERS 1 + #define NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS 1 + +#else + #error NANOPRINTF_SIZE_REPORT unknown value +#endif + +#define NANOPRINTF_IMPLEMENTATION + +#include "../nanoprintf.h" diff --git a/tests/unit_binary.cc b/tests/unit_binary.cc new file mode 100644 index 0000000..1a86f28 --- /dev/null +++ b/tests/unit_binary.cc @@ -0,0 +1,141 @@ +#include "unit_nanoprintf.h" +#include "doctest.h" + +#include +#include + +TEST_CASE("npf_bin_len") { + CHECK(npf_bin_len(0) == 1); + CHECK(npf_bin_len(1) == 1); + CHECK(npf_bin_len(0b10) == 2); + CHECK(npf_bin_len(0b100) == 3); + CHECK(npf_bin_len(0b1000) == 4); + CHECK(npf_bin_len(0b10000) == 5); + CHECK(npf_bin_len(0b100000) == 6); + CHECK(npf_bin_len(0b1000000) == 7); + CHECK(npf_bin_len(0b10000000) == 8); + CHECK(npf_bin_len(0b100000001) == 9); + CHECK(npf_bin_len(0b1000000001) == 10); + CHECK(npf_bin_len(0b10000000001) == 11); + CHECK(npf_bin_len(0b100000000001) == 12); + CHECK(npf_bin_len(0b1000000000000) == 13); + CHECK(npf_bin_len(0b10000000000000) == 14); + CHECK(npf_bin_len(0b100000000000000) == 15); + CHECK(npf_bin_len(0b1000000000000000) == 16); + CHECK(npf_bin_len(0x80000000UL) == 32); + +#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1 + CHECK(npf_bin_len(0x8000000000ULL) == 40); + CHECK(npf_bin_len(0x800000000000ULL) == 48); + CHECK(npf_bin_len(0x80000000000000ULL) == 56); + CHECK(npf_bin_len(0x8000000000000000ULL) == 64); +#endif +} + +namespace { +void require_equal(char const *expected, char const *fmt, ...) { + char buf[256]; + + std::string npf_result; { + va_list args; + va_start(args, fmt); + npf_vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + buf[sizeof(buf)-1] = '\0'; + npf_result = buf; + } + + REQUIRE(npf_result == std::string{expected}); +} +} + +TEST_CASE("binary") { + SUBCASE("plain") { + require_equal( "0", "%b", 0); + require_equal( "1", "%b", 1); + require_equal( "10", "%b", 0b10); + require_equal( "11", "%b", 0b11); + require_equal( "100", "%b", 0b100); + require_equal( "101", "%b", 0b101); + require_equal( "110", "%b", 0b110); + require_equal( "110", "%b", 0b110); + require_equal( "111", "%b", 0b111); + require_equal("1000", "%b", 0b1000); + require_equal("10000", "%b", 0b10000); + require_equal("100000", "%b", 0b100000); + require_equal("1000000", "%b", 0b1000000); + require_equal("10000000", "%b", 0b10000000); + require_equal( "10010001101000101011001111000", "%b", 0x12345678); + require_equal( "1010101010101010101010101010101", "%b", 0x55555555); + require_equal("10101010101010101010101010101010", "%b", 0xAAAAAAAA); + require_equal("11111111111111111111111111111111", "%b", 0xFFFFFFFF); + } + + SUBCASE("length") { + require_equal("11111111", "%hhb", 0xFFFFFFFF); // char + require_equal("1111111111111111", "%hb", 0xFFFFFFFF); // short + } + +#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1 + SUBCASE("precision") { + require_equal( "", "%.0b", 0); + require_equal( "0", "%.1b", 0); + require_equal( "00", "%.2b", 0); + require_equal( "000", "%.3b", 0); + require_equal( "0000", "%.4b", 0); + require_equal( "00000", "%.5b", 0); + require_equal( "000000", "%.6b", 0); + require_equal( "0000000", "%.7b", 0); + require_equal( "00000000", "%.8b", 0); + require_equal( "000000000", "%.9b", 0); + require_equal( "0000000000", "%.10b", 0); + require_equal( "00000000000", "%.11b", 0); + require_equal( "000000000000", "%.12b", 0); + require_equal( "0000000000000", "%.13b", 0); + require_equal( "00000000000000", "%.14b", 0); + require_equal( "000000000000000", "%.15b", 0); + require_equal("0000000000000000", "%.16b", 0); + require_equal("00001111", "%.8b", 0b1111); + + require_equal("00000000000000000000000000000000", "%.32b", 0); + } +#endif + +#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1 + SUBCASE("field width") { + require_equal( "0", "%1b", 0); + require_equal(" 0", "%4b", 0); + require_equal(" 11", "%4b", 0b11); + } +#endif + +#if NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS == 1 + SUBCASE("large") { + require_equal("100000000000000000000000000000000", "%llb", 0x100000000ULL); + require_equal("100000000000000000000000000000001", "%llb", 0x100000001ULL); + require_equal("111111111111111111111111111111111111", "%llb", 0xFFFFFFFFFULL); + + require_equal("1000000000000000000000000000000000000000000000000000000000000000", + "%llb", + std::numeric_limits::min()); + + require_equal( "111111111111111111111111111111111111111111111111111111111111111", + "%llb", + std::numeric_limits::max()); + } +#endif + + SUBCASE("alternate form") { + require_equal("0", "%#b", 0); + require_equal("0b1", "%#b", 1); + require_equal("0B1", "%#B", 1); + require_equal("0b110101", "%#b", 0b110101); +#if NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS == 1 + require_equal(" 0", "%#8b", 0); + require_equal(" 0b11", "%#8b", 0b11); +#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1 + require_equal(" 0b0001", "%#10.4b", 1); +#endif +#endif + } +} diff --git a/tests/unit_bufputc.cc b/tests/unit_bufputc.cc index db9a82b..41dab0a 100644 --- a/tests/unit_bufputc.cc +++ b/tests/unit_bufputc.cc @@ -1,11 +1,4 @@ -#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_WRITEBACK_FORMAT_SPECIFIERS 1 -#define NANOPRINTF_VISIBILITY_STATIC -#define NANOPRINTF_IMPLEMENTATION -#include "../nanoprintf.h" - +#include "unit_nanoprintf.h" #include "doctest.h" #include diff --git a/tests/unit_fsplit_abs.cc b/tests/unit_fsplit_abs.cc index 59e73a0..91c2b37 100644 --- a/tests/unit_fsplit_abs.cc +++ b/tests/unit_fsplit_abs.cc @@ -1,11 +1,4 @@ -#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_WRITEBACK_FORMAT_SPECIFIERS 1 -#define NANOPRINTF_VISIBILITY_STATIC -#define NANOPRINTF_IMPLEMENTATION -#include "../nanoprintf.h" - +#include "unit_nanoprintf.h" #include "doctest.h" #include diff --git a/tests/unit_ftoa_rev.cc b/tests/unit_ftoa_rev.cc index f327104..fc8dcc6 100644 --- a/tests/unit_ftoa_rev.cc +++ b/tests/unit_ftoa_rev.cc @@ -1,11 +1,4 @@ -#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_WRITEBACK_FORMAT_SPECIFIERS 1 -#define NANOPRINTF_VISIBILITY_STATIC -#define NANOPRINTF_IMPLEMENTATION -#include "../nanoprintf.h" - +#include "unit_nanoprintf.h" #include "doctest.h" #include diff --git a/tests/unit_itoa_rev.cc b/tests/unit_itoa_rev.cc index 3c2b11b..1be11cf 100644 --- a/tests/unit_itoa_rev.cc +++ b/tests/unit_itoa_rev.cc @@ -1,11 +1,4 @@ -#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_WRITEBACK_FORMAT_SPECIFIERS 1 -#define NANOPRINTF_VISIBILITY_STATIC -#define NANOPRINTF_IMPLEMENTATION -#include "../nanoprintf.h" - +#include "unit_nanoprintf.h" #include "doctest.h" #include diff --git a/tests/unit_nanoprintf.h b/tests/unit_nanoprintf.h new file mode 100644 index 0000000..7378afc --- /dev/null +++ b/tests/unit_nanoprintf.h @@ -0,0 +1,12 @@ +#pragma once + +// Each unit test file compiles nanoprintf privately for access to helper functions. +#define NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS 1 +#define NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS 1 +#define NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS 1 +#define NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS 1 +#define NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS 1 +#define NANOPRINTF_VISIBILITY_STATIC +#define NANOPRINTF_IMPLEMENTATION + +#include "../nanoprintf.h" diff --git a/tests/unit_parse_format_spec.cc b/tests/unit_parse_format_spec.cc index bbd67ec..b8517be 100644 --- a/tests/unit_parse_format_spec.cc +++ b/tests/unit_parse_format_spec.cc @@ -1,11 +1,4 @@ -#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_WRITEBACK_FORMAT_SPECIFIERS 1 -#define NANOPRINTF_VISIBILITY_STATIC -#define NANOPRINTF_IMPLEMENTATION -#include "../nanoprintf.h" - +#include "unit_nanoprintf.h" #include "doctest.h" #include @@ -16,9 +9,6 @@ TEST_CASE("npf_parse_format_spec") { npf_format_spec_t spec; memset(&spec, 0xCD, sizeof(spec)); - REQUIRE(!npf_parse_format_spec("abcd", &spec)); // first char not % - REQUIRE(!npf_parse_format_spec("%", &spec)); // % ends string - SUBCASE("Optional flags") { // Printf behavior is specified in ISO/IEC 9899:201x 7.21.6.1 // Optional flags are defined in 7.21.6.1.6 @@ -481,6 +471,13 @@ TEST_CASE("npf_parse_format_spec") { REQUIRE(spec.conv_spec == NPF_FMT_SPEC_CONV_FLOAT_DECIMAL); REQUIRE(spec.conv_spec_case == NPF_FMT_SPEC_CONV_CASE_UPPER); } + +#if NANOPRINTF_USE_BINARY_FORMAT_SPECIFIERS == 1 + SUBCASE("b") { + REQUIRE(npf_parse_format_spec("%b", &spec) == 2); + REQUIRE(spec.conv_spec == NPF_FMT_SPEC_CONV_BINARY); + } +#endif } } diff --git a/tests/unit_snprintf.cc b/tests/unit_snprintf.cc index 9ecc647..3002c3c 100644 --- a/tests/unit_snprintf.cc +++ b/tests/unit_snprintf.cc @@ -1,11 +1,4 @@ -#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_WRITEBACK_FORMAT_SPECIFIERS 1 -#define NANOPRINTF_VISIBILITY_STATIC -#define NANOPRINTF_IMPLEMENTATION -#include "../nanoprintf.h" - +#include "unit_nanoprintf.h" #include "doctest.h" #include diff --git a/tests/unit_utoa_rev.cc b/tests/unit_utoa_rev.cc index 5de0be6..f0157c8 100644 --- a/tests/unit_utoa_rev.cc +++ b/tests/unit_utoa_rev.cc @@ -1,11 +1,4 @@ -#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_WRITEBACK_FORMAT_SPECIFIERS 1 -#define NANOPRINTF_VISIBILITY_STATIC -#define NANOPRINTF_IMPLEMENTATION -#include "../nanoprintf.h" - +#include "unit_nanoprintf.h" #include "doctest.h" #include diff --git a/tests/unit_vpprintf.cc b/tests/unit_vpprintf.cc index c9d7fbe..2ab13b2 100644 --- a/tests/unit_vpprintf.cc +++ b/tests/unit_vpprintf.cc @@ -1,11 +1,4 @@ -#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_WRITEBACK_FORMAT_SPECIFIERS 1 -#define NANOPRINTF_VISIBILITY_STATIC -#define NANOPRINTF_IMPLEMENTATION -#include "../nanoprintf.h" - +#include "unit_nanoprintf.h" #include "doctest.h" #include