├── .adr-dir ├── .clang-format ├── .gitignore ├── .gitmodules ├── LICENSE ├── Makefile ├── README.md ├── advanced_usage.md ├── examples ├── default_to_serial │ └── default_to_serial.ino ├── override_putchar │ └── override_putchar.ino └── specify_print_class │ └── specify_print_class.ino ├── extras ├── docs │ └── adr │ │ ├── 0001-record-architecture-decisions.md │ │ └── 0002-library-source-reorganization-to-support-avr-default-settings.md ├── printf │ ├── printf.c │ └── printf.h ├── sketch_cpp │ ├── default_to_serial.cpp │ ├── override_putchar.cpp │ └── specify_print_class.cpp └── test │ └── LibPrintf.h ├── library.json ├── library.properties ├── meson.build ├── src ├── LibPrintf.cpp └── LibPrintf.h ├── subprojects └── arduinocore-avr.wrap └── tools ├── CI.jenkinsfile └── Jenkinsfile /.adr-dir: -------------------------------------------------------------------------------- 1 | extras/docs/adr/ 2 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlinesLeft: true 9 | AlignOperands: true 10 | AlignTrailingComments: false 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortIfStatementsOnASingleLine: false 15 | AllowShortFunctionsOnASingleLine: Empty 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: false 20 | AlwaysBreakTemplateDeclarations: true 21 | BinPackArguments: true 22 | BinPackParameters: true 23 | BraceWrapping: 24 | AfterClass: true 25 | AfterControlStatement: true 26 | AfterEnum: true 27 | AfterFunction: true 28 | AfterNamespace: true 29 | AfterObjCDeclaration: false 30 | AfterStruct: true 31 | AfterUnion: true 32 | BeforeCatch: true 33 | BeforeElse: true 34 | IndentBraces: false 35 | BreakBeforeBinaryOperators: None 36 | BreakBeforeBraces: Custom 37 | BreakBeforeTernaryOperators: true 38 | BreakConstructorInitializersBeforeComma: false 39 | BreakAfterJavaFieldAnnotations: false 40 | BreakStringLiterals: true 41 | ColumnLimit: 100 42 | CommentPragmas: '^ IWYU pragma:' 43 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 44 | ConstructorInitializerIndentWidth: 4 45 | ContinuationIndentWidth: 4 46 | Cpp11BracedListStyle: true 47 | DerivePointerAlignment: false 48 | DisableFormat: false 49 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 50 | IncludeCategories: 51 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 52 | Priority: 2 53 | - Regex: '^(<|"(gtest|isl|json)/)' 54 | Priority: 3 55 | - Regex: '.*' 56 | Priority: 1 57 | IncludeIsMainRegex: '$' 58 | IndentCaseLabels: true 59 | IndentWidth: 4 60 | IndentWrappedFunctionNames: true 61 | JavaScriptQuotes: Leave 62 | KeepEmptyLinesAtTheStartOfBlocks: false 63 | MacroBlockBegin: '' 64 | MacroBlockEnd: '' 65 | MaxEmptyLinesToKeep: 1 66 | NamespaceIndentation: Inner 67 | PenaltyBreakBeforeFirstCallParameter: 19 68 | PenaltyBreakComment: 300 69 | PenaltyBreakFirstLessLess: 120 70 | PenaltyBreakString: 1000 71 | PenaltyExcessCharacter: 1000000 72 | PenaltyReturnTypeOnItsOwnLine: 60 73 | PointerAlignment: Left 74 | ReflowComments: true 75 | SortIncludes: true 76 | SpaceAfterCStyleCast: false 77 | SpaceAfterTemplateKeyword: false 78 | SpaceBeforeAssignmentOperators: true 79 | SpaceBeforeParens: Never 80 | SpaceInEmptyParentheses: false 81 | SpacesBeforeTrailingComments: 1 82 | SpacesInAngles: false 83 | SpacesInContainerLiterals: false 84 | SpacesInCStyleCastParentheses: false 85 | SpacesInParentheses: false 86 | SpacesInSquareBrackets: false 87 | Standard: Cpp11 88 | TabWidth: 4 89 | UseTab: Always 90 | ... 91 | 92 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated files 2 | buildresults/** 3 | 4 | # Meson subprojects 5 | **/subprojects/** 6 | !**/subprojects/*.wrap 7 | 8 | # Prerequisites 9 | *.d 10 | 11 | # Compiled Object files 12 | *.slo 13 | *.lo 14 | *.o 15 | *.obj 16 | 17 | # Precompiled Headers 18 | *.gch 19 | *.pch 20 | 21 | # Compiled Dynamic libraries 22 | *.so 23 | *.dylib 24 | *.dll 25 | 26 | # Fortran module files 27 | *.mod 28 | *.smod 29 | 30 | # Compiled Static libraries 31 | *.lai 32 | *.la 33 | *.a 34 | *.lib 35 | 36 | # Executables 37 | *.exe 38 | *.out 39 | *.app 40 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "build"] 2 | path = build 3 | url = https://github.com/embeddedartistry/meson-buildsystem.git 4 | [submodule "extras/printf/printf-submodule"] 5 | path = extras/printf/printf-submodule 6 | url = https://github.com/embeddedartistry/printf.git 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Embedded Artistry 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # you can set this to 1 to see all commands that are being run 2 | VERBOSE ?= 0 3 | 4 | ifeq ($(VERBOSE),1) 5 | export Q := 6 | export VERBOSE := 1 7 | else 8 | export Q := @ 9 | export VERBOSE := 0 10 | endif 11 | 12 | BUILDRESULTS ?= buildresults 13 | CONFIGURED_BUILD_DEP = $(BUILDRESULTS)/build.ninja 14 | 15 | # Override to provide your own settings to the shim 16 | OPTIONS ?= 17 | LTO ?= 0 18 | CROSS ?= 19 | NATIVE ?= 20 | DEBUG ?= 0 21 | SANITIZER ?= none 22 | INTERNAL_OPTIONS = 23 | 24 | ifeq ($(LTO),1) 25 | INTERNAL_OPTIONS += -Db_lto=true -Ddisable-builtins=true 26 | endif 27 | 28 | ifneq ($(CROSS),) 29 | # Split into two strings, first is arch, second is chip 30 | CROSS_2 := $(subst :, ,$(CROSS)) 31 | INTERNAL_OPTIONS += $(foreach FILE,$(CROSS_2),--cross-file=meson/cross/$(FILE).txt) 32 | endif 33 | 34 | ifneq ($(NATIVE),) 35 | # Split into words delimited by : 36 | NATIVE_2 := $(subst :, ,$(NATIVE)) 37 | INTERNAL_OPTIONS += $(foreach FILE,$(NATIVE_2),--native-file=meson/native/$(FILE).txt) 38 | endif 39 | 40 | ifeq ($(DEBUG),1) 41 | INTERNAL_OPTIONS += '-Ddebug=true -Doptimization=g' 42 | endif 43 | 44 | ifneq ($(SANITIZER),none) 45 | INTERNAL_OPTIONS += -Db_sanitize=$(SANITIZER) -Db_lundef=false 46 | endif 47 | 48 | all: default 49 | 50 | .PHONY: default 51 | default: | $(CONFIGURED_BUILD_DEP) 52 | $(Q)ninja -C $(BUILDRESULTS) 53 | 54 | # Manually Reconfigure a target, esp. with new options 55 | .PHONY: reconfig 56 | reconfig: 57 | $(Q) meson $(BUILDRESULTS) --reconfigure $(INTERNAL_OPTIONS) $(OPTIONS) 58 | 59 | # Runs whenever the build has not been configured successfully 60 | $(CONFIGURED_BUILD_DEP): 61 | $(Q) meson $(BUILDRESULTS) $(INTERNAL_OPTIONS) $(OPTIONS) 62 | 63 | .PHONY: clean 64 | clean: 65 | $(Q) if [ -d "$(BUILDRESULTS)" ]; then ninja -C buildresults clean; fi 66 | 67 | .PHONY: distclean 68 | distclean: 69 | $(Q) rm -rf $(BUILDRESULTS) 70 | 71 | ### Help Output ### 72 | .PHONY : help 73 | help : 74 | @echo "usage: make [OPTIONS] " 75 | @echo " Options:" 76 | @echo " > VERBOSE Show verbose output for Make rules. Default 0. Enable with 1." 77 | @echo " > BUILDRESULTS Directory for build results. Default buildresults." 78 | @echo " > OPTIONS Configuration options to pass to a build. Default empty." 79 | @echo " > LTO Enable LTO builds. Default 0. Enable with 1." 80 | @echo " > DEBUG Enable a debug build. Default 0 (release). Enable with 1." 81 | @echo " > CROSS Enable a Cross-compilation build. Default format is arch:chip." 82 | @echo " - Example: make CROSS=arm:cortex-m3" 83 | @echo " - For supported chips, see meson/cross/" 84 | @echo " - Additional files can be layered by adding additional" 85 | @echo " args separated by ':'" 86 | @echo " > NATIVE Supply an alternative native toolchain by name." 87 | @echo " - Example: make NATIVE=gcc-9" 88 | @echo " - Additional files can be layered by adding additional" 89 | @echo " args separated by ':'" 90 | @echo " - Example: make NATIVE=gcc-9:gcc-gold" 91 | @echo " > SANITIZER Compile with support for a Clang/GCC Sanitizer." 92 | @echo " Options are: none (default), address, thread, undefined, memory," 93 | @echo " and address,undefined' as a combined option" 94 | @echo "Targets:" 95 | @echo " default: Builds all default targets ninja knows about" 96 | @echo " clean: cleans build artifacts, keeping build files in place" 97 | @echo " distclean: removes the configured build output directory" 98 | @echo " reconfig: Reconfigure an existing build output folder with new settings" 99 | 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Arduino Printf 2 | 3 | This library adds support for the `printf()` function to Arduino projects. This code leverages the [embeddedartistry/printf](https://github.com/embeddedartistry/printf) library (a fork of [eyalroz/printf](https://github.com/eyalroz/printf), which is designed for use in embedded systems. For more information about what is available, please refer to the [parent library documentation](https://github.com/embeddedartistry/printf/blob/master/README.md). 4 | 5 | ## What This Library Provides 6 | 7 | This library provides a standalone implementation for the following functions: 8 | 9 | * `printf()` 10 | * `sprintf()` and `snprintf()` 11 | * `vprintf()` and `vsnprintf()` 12 | 13 | ## Project Target 14 | 15 | This library aims to offer a complete `printf()` solution while maintaining low storage and RAM requirements. 16 | This is **critical** for MCUs with limited storage and RAM. This project is ideal for [AVR based MCUs](https://en.wikipedia.org/wiki/AVR_microcontrollers) like the 17 | Arduino Uno and it's siblings. 18 | 19 | ## ESP8266 and ESP32 20 | 21 | The Arduino implementations for the [ESP8266](https://github.com/esp8266/Arduino) and 22 | [ESP32](https://github.com/espressif/arduino-esp32) already include a `printf()` implementation 23 | as part of the base library. You do not need this library for those platforms. 24 | 25 | ## Using the Library 26 | 27 | To use this library in your Arduino project, you need to include the header: 28 | 29 | ``` 30 | #include 31 | 32 | void setup() { 33 | Serial.begin(115200); 34 | } 35 | ``` 36 | 37 | By default, the library can be used without any special initialization. Any `printf()` calls will be output using 38 | the Arduino `Serial` interface. If you need to use a different interface, call `printf_init`. 39 | 40 | If you only want to use `s[n]printf`, then you do not need to initialize the library. 41 | 42 | ## Advanced Usage 43 | 44 | See [advanced_usage.md](advanced_usage.md). 45 | 46 | ## Configuration 47 | 48 | If memory footprint is critical, you can disable library features using compiler definitions. Available controls are: 49 | 50 | * `PRINTF_DISABLE_ALL` 51 | - Remove all `printf` calls from the program 52 | * `PRINTF_NTOA_BUFFER_SIZE` (unsigned integer) 53 | * 'ntoa' conversion buffer size, this must be big enough to hold one converted numeric number including padded zeros (dynamically created on stack) 54 | * Default: 32 bytes 55 | * `PRINTF_FTOA_BUFFER_SIZE` (unsigned integer) 56 | - 'ftoa' conversion buffer size, this must be big enough to hold one converted float number including padded zeros (dynamically created on stack) 57 | - Default: 32 bytes 58 | * `PRINTF_DISABLE_SUPPORT_FLOAT` 59 | - support for the floating point type (%f) 60 | * `PRINTF_DISABLE_SUPPORT_EXPONENTIAL` 61 | - support for exponential floating point notation (%e/%g) 62 | - Default: active 63 | * `PRINTF_DEFAULT_FLOAT_PRECISION` (unsigned integer) 64 | * `PRINTF_MAX_FLOAT` (float value) 65 | - define the largest float suitable to print with %f 66 | - Default: active 67 | - 1e9 68 | * `PRINTF_DISABLE_SUPPORT_LONG_LONG` 69 | - support for the long long types (%llu or %p) 70 | * Default: active 71 | * `PRINTF_DISABLE_SUPPORT_PTRDIFF_T` 72 | - support for the ptrdiff_t type (%t) 73 | - Default: active 74 | 75 | For AVR chips, the library will automatically set `PRINTF_DISABLE_SUPPORT_EXPONENTIAL` and `PRINTF_DISABLE_SUPPORT_LONG_LONG`. You can re-enable these settings by defining `PRINTF_PREVENT_DEFAULT_AVR_SETTINGS`. 76 | 77 | Because these settings control behavior in the source file, they cannot be defined in the sketch. You must adjust the compilation commands for your project in order for the changes to take effect. 78 | 79 | If you're using a Makefile or other build system, you'd use the `-D` flag (e.g., `-DPRINTF_DISABLE_SUPPORT_EXPONENTIAL`) to the library build target. For Arduino IDE, the flags need to be added to the compiler.extra_flags property in [platform.txt](https://arduino.github.io/arduino-cli/platform-specification/#platformtxt) or [platform.local.txt](https://arduino.github.io/arduino-cli/platform-specification/#platformlocaltxt). You would need to restart the IDE for the changes to take effect. 80 | 81 | Here are comparisons for a simple test sketch showing the overall sketch size for different configurations: 82 | 83 | | Type | Bytes | 84 | | -------------- | ----- | 85 | | No Serial | 1606 | 86 | | All options enabled | 9476 | 87 | | Disable long long and exponential | 6328 | 88 | | Disable long long, float, and exponential | 4256 | 89 | 90 | ## Examples 91 | 92 | Multiple examples are provided with this library in the [examples/](examples/) folder. 93 | 94 | * [Default Usage](examples/default_to_serial/default_to_serial.ino) 95 | - Without any initialization, `Serial` will be the default output for `printf()` 96 | - This example initializes the `Serial` class and prints in a loop 97 | * [Specify Print Class](examples/specify_print_class/specify_print_class.ino) 98 | - Any class derived from the `Print` base class can be used with the **Arduino Printf** library 99 | - This example initializes `printf` with `Serial1` instead of `Serial` 100 | * [Override Putchar](examples/override_putchar/override_putchar.ino) 101 | - This example overrides `putchar_()` and adds a space in between every letter 102 | - You can implement any kind of logic within `putchar_()` that you like, such as outputting information to multiple ports 103 | -------------------------------------------------------------------------------- /advanced_usage.md: -------------------------------------------------------------------------------- 1 | ## Advanced Usage 2 | 3 | You can include `printf.h` directly and supply your own implementation of `putchar_()`. This approach is useful if you want to use the library in a test suite (skipping Arduino SDK headers). 4 | 5 | ### Changing Default Output Target 6 | 7 | You can specify any class derived from the `Print` base class for use with `printf()`. To change the output class, use the `printf_init()` function in `setup()`: 8 | 9 | ``` 10 | printf_init(Serial1); 11 | Serial1.begin(115200); 12 | ``` 13 | 14 | ### More Complicated Output Scenarios 15 | 16 | More complicated logic is possible, such as sending `printf()` output to multiple locations. The `mpaland/printf` library requires that the end-user defines a `putchar_()` function, which is used by all other library functions. There is an example script that explains this. 17 | -------------------------------------------------------------------------------- /examples/default_to_serial/default_to_serial.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * This sketch demonstrates using the ArduinoPrintf library with the default setup, 3 | * which requires no initialization. The Serial class will be used as the output 4 | * for the ArduinoPrintf library. 5 | * 6 | * You are still responsible for initializing the Print class in setup(), the 7 | * ArduinoPrintf library will not do that for you. 8 | */ 9 | 10 | #include 11 | 12 | void setup() { 13 | Serial.begin(115200); 14 | } 15 | 16 | void loop() { 17 | // put your main code here, to run repeatedly: 18 | printf("I'm alive!\n"); 19 | delay(1000); 20 | } 21 | -------------------------------------------------------------------------------- /examples/override_putchar/override_putchar.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * This sketch demonstrates the ability to override the ArduinoPrintf library's 3 | * default implementation of putchar_() with a custom user implementation. 4 | * You can do anything you want in your putchar_() function: output to multiple ports, 5 | * filter data, add extra characters, etc. 6 | */ 7 | 8 | #include 9 | 10 | void setup() { 11 | // But the important detail: you are responsible for initializing the interface! 12 | Serial.begin(115200); 13 | } 14 | 15 | void putchar_(char character) 16 | { 17 | Serial.print(character); 18 | Serial.print(' '); 19 | } 20 | 21 | void loop() { 22 | printf("I'm alive!\n"); 23 | delay(1000); 24 | } 25 | -------------------------------------------------------------------------------- /examples/specify_print_class/specify_print_class.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * This sketch demonstrates using the ArduinoPrintf library with a custom 3 | * Print class of your choice. Call printf_init() with the desired output class. 4 | * Any class which has Print as a base class can be used. 5 | * 6 | * You are still responsible for initializing the Print class in setup(), the 7 | * ArduinoPrintf library will not do that for you. 8 | */ 9 | 10 | #include 11 | 12 | void setup() { 13 | // Specify the print class to use with printf(). 14 | // Any class derived from Print will work. 15 | printf_init(Serial1); 16 | 17 | // But the important detail: you are responsible for initializing the interface! 18 | Serial1.begin(115200); 19 | } 20 | 21 | void loop() { 22 | printf("I'm alive!\n"); 23 | delay(1000); 24 | } 25 | -------------------------------------------------------------------------------- /extras/docs/adr/0001-record-architecture-decisions.md: -------------------------------------------------------------------------------- 1 | # 1. Record architecture decisions 2 | 3 | Date: 2020-05-26 4 | 5 | ## Status 6 | 7 | Accepted 8 | 9 | ## Context 10 | 11 | We need to record the architectural decisions made on this project. 12 | 13 | ## Decision 14 | 15 | We will use Architecture Decision Records, as [described by Michael Nygard](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions). 16 | 17 | ## Consequences 18 | 19 | See Michael Nygard's article, linked above. For a lightweight ADR toolset, see Nat Pryce's [adr-tools](https://github.com/npryce/adr-tools). 20 | -------------------------------------------------------------------------------- /extras/docs/adr/0002-library-source-reorganization-to-support-avr-default-settings.md: -------------------------------------------------------------------------------- 1 | # 2. Library source reorganization to support AVR default settings 2 | 3 | Date: 2020-05-26 4 | 5 | ## Status 6 | 7 | Accepted 8 | 9 | ## Context 10 | 11 | We want to provide default settings for AVR chips that reduces the library size *without modifying the original `printf` library files*. We can do this by including the `.c` file directly after we specify the default settings. However, this results in duplicate symbols when the `printf` library files are contained in the `src/` directory, because the Arduino IDE automatically builds them. We need a way to prevent the IDE from automatically building the files so we do not end up with duplicate symbols. 12 | 13 | ## Decision 14 | 15 | * The `src/` folder contains `LibPrintf.cpp` and `LibPrintf.h` 16 | * The `extras/printf` folder contains `printf.c` and `printf.h` as files directly within the repository, enabling the library to work without submodule 17 | * `LibPrintf.h` will include `../extras/printf/printf.h` to access the `printf` definitions 18 | * `LibPrintf.cpp` will include `../extras/printf/printf.c` to add the symbols for the `printf` library. We will also add our definition defaults for `__AVR__`, ensuring that the settings are applied automatically without modifying build rules 19 | 20 | ## Consequences 21 | 22 | * We can modify default settings without changing the original library files, which we do not own or maintain. 23 | * We are referencing source code in an Arduino library build that is kept outside of the `src/` folder, which can be confusing to users. This might also break in the future. 24 | -------------------------------------------------------------------------------- /extras/printf/printf.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @author (c) Eyal Rozenberg 3 | * 2021, Haifa, Palestine/Israel 4 | * @author (c) Marco Paland (info@paland.com) 5 | * 2014-2019, PALANDesign Hannover, Germany 6 | * 7 | * @note Others have made smaller contributions to this file: see the 8 | * contributors page at https://github.com/eyalroz/printf/graphs/contributors 9 | * or ask one of the authors. 10 | * 11 | * @brief Small stand-alone implementation of the printf family of functions 12 | * (`(v)printf`, `(v)s(n)printf` etc., geared towards use on embedded systems with 13 | * a very limited resources. 14 | * 15 | * @note the implementations are thread-safe; re-entrant; use no functions from 16 | * the standard library; and do not dynamically allocate any memory. 17 | * 18 | * @license The MIT License (MIT) 19 | * 20 | * Permission is hereby granted, free of charge, to any person obtaining a copy 21 | * of this software and associated documentation files (the "Software"), to deal 22 | * in the Software without restriction, including without limitation the rights 23 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 24 | * copies of the Software, and to permit persons to whom the Software is 25 | * furnished to do so, subject to the following conditions: 26 | * 27 | * The above copyright notice and this permission notice shall be included in 28 | * all copies or substantial portions of the Software. 29 | * 30 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 31 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 32 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 33 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 34 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 35 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 36 | * THE SOFTWARE. 37 | */ 38 | 39 | #include 40 | #include 41 | 42 | // Define this globally (e.g. gcc -DPRINTF_INCLUDE_CONFIG_H ...) to include the 43 | // printf_config.h header file 44 | #ifndef PRINTF_INCLUDE_CONFIG_H 45 | #define PRINTF_INCLUDE_CONFIG_H 0 46 | #endif 47 | 48 | #if PRINTF_INCLUDE_CONFIG_H 49 | #include "printf_config.h" 50 | #endif 51 | 52 | #include "printf.h" 53 | 54 | #if PRINTF_ALIAS_STANDARD_FUNCTION_NAMES 55 | # define printf_ printf 56 | # define sprintf_ sprintf 57 | # define vsprintf_ vsprintf 58 | # define snprintf_ snprintf 59 | # define vsnprintf_ vsnprintf 60 | # define vprintf_ vprintf 61 | #endif 62 | 63 | 64 | // 'ntoa' conversion buffer size, this must be big enough to hold one converted 65 | // numeric number including padded zeros (dynamically created on stack) 66 | #ifndef PRINTF_INTEGER_BUFFER_SIZE 67 | #define PRINTF_INTEGER_BUFFER_SIZE 32 68 | #endif 69 | 70 | // 'ftoa' conversion buffer size, this must be big enough to hold one converted 71 | // float number including padded zeros (dynamically created on stack) 72 | #ifndef PRINTF_FTOA_BUFFER_SIZE 73 | #define PRINTF_FTOA_BUFFER_SIZE 32 74 | #endif 75 | 76 | // Support for the decimal notation floating point conversion specifiers (%f, %F) 77 | #ifndef PRINTF_SUPPORT_DECIMAL_SPECIFIERS 78 | #define PRINTF_SUPPORT_DECIMAL_SPECIFIERS 1 79 | #endif 80 | 81 | // Support for the exponential notation floating point conversion specifiers (%e, %g, %E, %G) 82 | #ifndef PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS 83 | #define PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS 1 84 | #endif 85 | 86 | // Support for the length write-back specifier (%n) 87 | #ifndef PRINTF_SUPPORT_WRITEBACK_SPECIFIER 88 | #define PRINTF_SUPPORT_WRITEBACK_SPECIFIER 1 89 | #endif 90 | 91 | // Default precision for the floating point conversion specifiers (the C standard sets this at 6) 92 | #ifndef PRINTF_DEFAULT_FLOAT_PRECISION 93 | #define PRINTF_DEFAULT_FLOAT_PRECISION 6 94 | #endif 95 | 96 | // According to the C languages standard, printf() and related functions must be able to print any 97 | // integral number in floating-point notation, regardless of length, when using the %f specifier - 98 | // possibly hundreds of characters, potentially overflowing your buffers. In this implementation, 99 | // all values beyond this threshold are switched to exponential notation. 100 | #ifndef PRINTF_MAX_INTEGRAL_DIGITS_FOR_DECIMAL 101 | #define PRINTF_MAX_INTEGRAL_DIGITS_FOR_DECIMAL 9 102 | #endif 103 | 104 | // Support for the long long integral types (with the ll, z and t length modifiers for specifiers 105 | // %d,%i,%o,%x,%X,%u, and with the %p specifier). Note: 'L' (long double) is not supported. 106 | #ifndef PRINTF_SUPPORT_LONG_LONG 107 | #define PRINTF_SUPPORT_LONG_LONG 1 108 | #endif 109 | 110 | #if PRINTF_SUPPORT_LONG_LONG 111 | typedef unsigned long long printf_unsigned_value_t; 112 | typedef long long printf_signed_value_t; 113 | #else 114 | typedef unsigned long printf_unsigned_value_t; 115 | typedef long printf_signed_value_t; 116 | #endif 117 | 118 | #define PRINTF_PREFER_DECIMAL false 119 | #define PRINTF_PREFER_EXPONENTIAL true 120 | 121 | /////////////////////////////////////////////////////////////////////////////// 122 | 123 | // The following will convert the number-of-digits into an exponential-notation literal 124 | #define PRINTF_CONCATENATE(s1, s2) s1##s2 125 | #define PRINTF_EXPAND_THEN_CONCATENATE(s1, s2) PRINTF_CONCATENATE(s1, s2) 126 | #define PRINTF_FLOAT_NOTATION_THRESHOLD PRINTF_EXPAND_THEN_CONCATENATE(1e,PRINTF_MAX_INTEGRAL_DIGITS_FOR_DECIMAL) 127 | 128 | // internal flag definitions 129 | #define FLAGS_ZEROPAD (1U << 0U) 130 | #define FLAGS_LEFT (1U << 1U) 131 | #define FLAGS_PLUS (1U << 2U) 132 | #define FLAGS_SPACE (1U << 3U) 133 | #define FLAGS_HASH (1U << 4U) 134 | #define FLAGS_UPPERCASE (1U << 5U) 135 | #define FLAGS_CHAR (1U << 6U) 136 | #define FLAGS_SHORT (1U << 7U) 137 | #define FLAGS_LONG (1U << 8U) 138 | #define FLAGS_LONG_LONG (1U << 9U) 139 | #define FLAGS_PRECISION (1U << 10U) 140 | #define FLAGS_ADAPT_EXP (1U << 11U) 141 | #define FLAGS_POINTER (1U << 12U) 142 | // Note: Similar, but not identical, effect as FLAGS_HASH 143 | 144 | #define BASE_BINARY 2 145 | #define BASE_OCTAL 8 146 | #define BASE_DECIMAL 10 147 | #define BASE_HEX 16 148 | 149 | typedef uint8_t numeric_base_t; 150 | 151 | #if (PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS) 152 | #include 153 | #if FLT_RADIX != 2 154 | #error "Non-binary-radix floating-point types are unsupported." 155 | #endif 156 | 157 | #if DBL_MANT_DIG == 24 158 | 159 | #define DOUBLE_SIZE_IN_BITS 32 160 | typedef uint32_t double_uint_t; 161 | #define DOUBLE_EXPONENT_MASK 0xFFU 162 | #define DOUBLE_BASE_EXPONENT 127 163 | 164 | #elif DBL_MANT_DIG == 53 165 | 166 | #define DOUBLE_SIZE_IN_BITS 64 167 | typedef uint64_t double_uint_t; 168 | #define DOUBLE_EXPONENT_MASK 0x7FFU 169 | #define DOUBLE_BASE_EXPONENT 1023 170 | 171 | #else 172 | #error "Unsupported double type configuration" 173 | #endif 174 | #define DOUBLE_STORED_MANTISSA_BITS (DBL_MANT_DIG - 1) 175 | 176 | typedef union { 177 | double_uint_t U; 178 | double F; 179 | } double_with_bit_access; 180 | 181 | // This is unnecessary in C99, since compound initializers can be used, 182 | // but: 1. Some compilers are finicky about this; 2. Some people may want to convert this to C89; 183 | // 3. If you try to use it as C++, only C++20 supports compound literals 184 | static inline double_with_bit_access get_bit_access(double x) 185 | { 186 | double_with_bit_access dwba; 187 | dwba.F = x; 188 | return dwba; 189 | } 190 | 191 | static inline int get_sign(double x) 192 | { 193 | // The sign is stored in the highest bit 194 | return get_bit_access(x).U >> (DOUBLE_SIZE_IN_BITS - 1); 195 | } 196 | 197 | static inline int get_exp2(double_with_bit_access x) 198 | { 199 | // The exponent in an IEEE-754 floating-point number occupies a contiguous 200 | // sequence of bits (e.g. 52..62 for 64-bit doubles), but with a non-trivial representation: An 201 | // unsigned offset from some negative value (with the extremal offset values reserved for 202 | // special use). 203 | return (int)((x.U >> DOUBLE_STORED_MANTISSA_BITS ) & DOUBLE_EXPONENT_MASK) - DOUBLE_BASE_EXPONENT; 204 | } 205 | #define PRINTF_ABS(_x) ( (_x) > 0 ? (_x) : -(_x) ) 206 | 207 | #endif // (PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS) 208 | 209 | // Note in particular the behavior here on LONG_MIN or LLONG_MIN; it is valid 210 | // and well-defined, but if you're not careful you can easily trigger undefined 211 | // behavior with -LONG_MIN or -LLONG_MIN 212 | #define ABS_FOR_PRINTING(_x) ((printf_unsigned_value_t) ( (_x) > 0 ? (_x) : -((printf_signed_value_t)_x) )) 213 | 214 | // output function type 215 | typedef void (*out_fct_type)(char character, void* buffer, size_t idx, size_t maxlen); 216 | 217 | 218 | // wrapper (used as buffer) for output function type 219 | typedef struct { 220 | void (*fct)(char character, void* arg); 221 | void* arg; 222 | } out_function_wrapper_type; 223 | 224 | 225 | // internal buffer output 226 | static inline void out_buffer(char character, void* buffer, size_t idx, size_t maxlen) 227 | { 228 | if (idx < maxlen) { 229 | ((char*)buffer)[idx] = character; 230 | } 231 | } 232 | 233 | 234 | // internal null output 235 | static inline void out_discard(char character, void* buffer, size_t idx, size_t maxlen) 236 | { 237 | (void)character; (void)buffer; (void)idx; (void)maxlen; 238 | } 239 | 240 | 241 | // internal putchar_ wrapper 242 | static inline void out_putchar(char character, void* buffer, size_t idx, size_t maxlen) 243 | { 244 | (void)buffer; (void)idx; (void)maxlen; 245 | if (character) { 246 | putchar_(character); 247 | } 248 | } 249 | 250 | 251 | // internal output function wrapper 252 | static inline void out_wrapped_function(char character, void* wrapped_function, size_t idx, size_t maxlen) 253 | { 254 | (void)idx; (void)maxlen; 255 | if (character) { 256 | // buffer is the output fct pointer 257 | ((out_function_wrapper_type*)wrapped_function)->fct(character, ((out_function_wrapper_type*)wrapped_function)->arg); 258 | } 259 | } 260 | 261 | 262 | // internal secure strlen 263 | // @return The length of the string (excluding the terminating 0) limited by 'maxsize' 264 | static inline unsigned int strnlen_s_(const char* str, size_t maxsize) 265 | { 266 | const char* s; 267 | for (s = str; *s && maxsize--; ++s); 268 | return (unsigned int)(s - str); 269 | } 270 | 271 | 272 | // internal test if char is a digit (0-9) 273 | // @return true if char is a digit 274 | static inline bool is_digit_(char ch) 275 | { 276 | return (ch >= '0') && (ch <= '9'); 277 | } 278 | 279 | 280 | // internal ASCII string to unsigned int conversion 281 | static unsigned int atoi_(const char** str) 282 | { 283 | unsigned int i = 0U; 284 | while (is_digit_(**str)) { 285 | i = i * 10U + (unsigned int)(*((*str)++) - '0'); 286 | } 287 | return i; 288 | } 289 | 290 | 291 | // output the specified string in reverse, taking care of any zero-padding 292 | static size_t out_rev_(out_fct_type out, char* buffer, size_t idx, size_t maxlen, const char* buf, size_t len, unsigned int width, unsigned int flags) 293 | { 294 | const size_t start_idx = idx; 295 | 296 | // pad spaces up to given width 297 | if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) { 298 | for (size_t i = len; i < width; i++) { 299 | out(' ', buffer, idx++, maxlen); 300 | } 301 | } 302 | 303 | // reverse string 304 | while (len) { 305 | out(buf[--len], buffer, idx++, maxlen); 306 | } 307 | 308 | // append pad spaces up to given width 309 | if (flags & FLAGS_LEFT) { 310 | while (idx - start_idx < width) { 311 | out(' ', buffer, idx++, maxlen); 312 | } 313 | } 314 | 315 | return idx; 316 | } 317 | 318 | 319 | // Invoked by print_integer after the actual number has been printed, performing necessary 320 | // work on the number's prefix (as the number is initially printed in reverse order) 321 | static size_t print_integer_finalization(out_fct_type out, char* buffer, size_t idx, size_t maxlen, char* buf, size_t len, bool negative, numeric_base_t base, unsigned int precision, unsigned int width, unsigned int flags) 322 | { 323 | size_t unpadded_len = len; 324 | 325 | // pad with leading zeros 326 | { 327 | if (!(flags & FLAGS_LEFT)) { 328 | if (width && (flags & FLAGS_ZEROPAD) && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { 329 | width--; 330 | } 331 | while ((flags & FLAGS_ZEROPAD) && (len < width) && (len < PRINTF_INTEGER_BUFFER_SIZE)) { 332 | buf[len++] = '0'; 333 | } 334 | } 335 | 336 | while ((len < precision) && (len < PRINTF_INTEGER_BUFFER_SIZE)) { 337 | buf[len++] = '0'; 338 | } 339 | 340 | if (base == BASE_OCTAL && (len > unpadded_len)) { 341 | // Since we've written some zeros, we've satisfied the alternative format leading space requirement 342 | flags &= ~FLAGS_HASH; 343 | } 344 | } 345 | 346 | // handle hash 347 | if (flags & (FLAGS_HASH | FLAGS_POINTER)) { 348 | if (!(flags & FLAGS_PRECISION) && len && ((len == precision) || (len == width))) { 349 | // Let's take back some padding digits to fit in what will eventually 350 | // be the format-specific prefix 351 | if (unpadded_len < len) { 352 | len--; 353 | } 354 | if (len && (base == BASE_HEX)) { 355 | if (unpadded_len < len) { 356 | len--; 357 | } 358 | } 359 | } 360 | if ((base == BASE_HEX) && !(flags & FLAGS_UPPERCASE) && (len < PRINTF_INTEGER_BUFFER_SIZE)) { 361 | buf[len++] = 'x'; 362 | } 363 | else if ((base == BASE_HEX) && (flags & FLAGS_UPPERCASE) && (len < PRINTF_INTEGER_BUFFER_SIZE)) { 364 | buf[len++] = 'X'; 365 | } 366 | else if ((base == BASE_BINARY) && (len < PRINTF_INTEGER_BUFFER_SIZE)) { 367 | buf[len++] = 'b'; 368 | } 369 | if (len < PRINTF_INTEGER_BUFFER_SIZE) { 370 | buf[len++] = '0'; 371 | } 372 | } 373 | 374 | if (len < PRINTF_INTEGER_BUFFER_SIZE) { 375 | if (negative) { 376 | buf[len++] = '-'; 377 | } 378 | else if (flags & FLAGS_PLUS) { 379 | buf[len++] = '+'; // ignore the space if the '+' exists 380 | } 381 | else if (flags & FLAGS_SPACE) { 382 | buf[len++] = ' '; 383 | } 384 | } 385 | 386 | return out_rev_(out, buffer, idx, maxlen, buf, len, width, flags); 387 | } 388 | 389 | // An internal itoa-like function 390 | static size_t print_integer(out_fct_type out, char* buffer, size_t idx, size_t maxlen, printf_unsigned_value_t value, bool negative, numeric_base_t base, unsigned int precision, unsigned int width, unsigned int flags) 391 | { 392 | char buf[PRINTF_INTEGER_BUFFER_SIZE]; 393 | size_t len = 0U; 394 | 395 | if (!value) { 396 | if ( !(flags & FLAGS_PRECISION) ) { 397 | buf[len++] = '0'; 398 | flags &= ~FLAGS_HASH; 399 | // We drop this flag this since either the alternative and regular modes of the specifier 400 | // don't differ on 0 values, or (in the case of octal) we've already provided the special 401 | // handling for this mode. 402 | } 403 | else if (base == BASE_HEX) { 404 | flags &= ~FLAGS_HASH; 405 | // We drop this flag this since either the alternative and regular modes of the specifier 406 | // don't differ on 0 values 407 | } 408 | } 409 | else { 410 | do { 411 | const char digit = (char)(value % base); 412 | buf[len++] = (char)(digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10); 413 | value /= base; 414 | } while (value && (len < PRINTF_INTEGER_BUFFER_SIZE)); 415 | } 416 | 417 | return print_integer_finalization(out, buffer, idx, maxlen, buf, len, negative, base, precision, width, flags); 418 | } 419 | 420 | #if (PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS) 421 | 422 | struct double_components { 423 | int_fast64_t integral; 424 | int_fast64_t fractional; 425 | bool is_negative; 426 | }; 427 | 428 | #define NUM_DECIMAL_DIGITS_IN_INT64_T 18 429 | #define PRINTF_MAX_PRECOMPUTED_POWER_OF_10 NUM_DECIMAL_DIGITS_IN_INT64_T 430 | static const double powers_of_10[NUM_DECIMAL_DIGITS_IN_INT64_T] = { 431 | 1e00, 1e01, 1e02, 1e03, 1e04, 1e05, 1e06, 1e07, 1e08, 432 | 1e09, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17 433 | }; 434 | 435 | #define PRINTF_MAX_SUPPORTED_PRECISION NUM_DECIMAL_DIGITS_IN_INT64_T - 1 436 | 437 | 438 | // Break up a double number - which is known to be a finite non-negative number - 439 | // into its base-10 parts: integral - before the decimal point, and fractional - after it. 440 | // Taken the precision into account, but does not change it even internally. 441 | static struct double_components get_components(double number, unsigned int precision) 442 | { 443 | struct double_components number_; 444 | number_.is_negative = get_sign(number); 445 | double abs_number = (number_.is_negative) ? -number : number; 446 | number_.integral = (int_fast64_t)abs_number; 447 | double remainder = (abs_number - number_.integral) * powers_of_10[precision]; 448 | number_.fractional = (int_fast64_t)remainder; 449 | 450 | remainder -= (double) number_.fractional; 451 | 452 | if (remainder > 0.5) { 453 | ++number_.fractional; 454 | // handle rollover, e.g. case 0.99 with precision 1 is 1.0 455 | if ((double) number_.fractional >= powers_of_10[precision]) { 456 | number_.fractional = 0; 457 | ++number_.integral; 458 | } 459 | } 460 | else if (remainder == 0.5) { 461 | if ((number_.fractional == 0U) || (number_.fractional & 1U)) { 462 | // if halfway, round up if odd OR if last digit is 0 463 | ++number_.fractional; 464 | } 465 | } 466 | 467 | if (precision == 0U) { 468 | remainder = abs_number - (double) number_.integral; 469 | if ((!(remainder < 0.5) || (remainder > 0.5)) && (number_.integral & 1)) { 470 | // exactly 0.5 and ODD, then round up 471 | // 1.5 -> 2, but 2.5 -> 2 472 | ++number_.integral; 473 | } 474 | } 475 | return number_; 476 | } 477 | 478 | struct scaling_factor { 479 | double raw_factor; 480 | bool multiply; // if true, need to multiply by raw_factor; otherwise need to divide by it 481 | }; 482 | 483 | double apply_scaling(double num, struct scaling_factor normalization) 484 | { 485 | return normalization.multiply ? num * normalization.raw_factor : num / normalization.raw_factor; 486 | } 487 | 488 | double unapply_scaling(double normalized, struct scaling_factor normalization) 489 | { 490 | return normalization.multiply ? normalized / normalization.raw_factor : normalized * normalization.raw_factor; 491 | } 492 | 493 | struct scaling_factor update_normalization(struct scaling_factor sf, double extra_multiplicative_factor) 494 | { 495 | struct scaling_factor result; 496 | if (sf.multiply) { 497 | result.multiply = true; 498 | result.raw_factor = sf.raw_factor * extra_multiplicative_factor; 499 | } 500 | else { 501 | int factor_exp2 = get_exp2(get_bit_access(sf.raw_factor)); 502 | int extra_factor_exp2 = get_exp2(get_bit_access(extra_multiplicative_factor)); 503 | 504 | // Divide the larger-exponent raw raw_factor by the smaller 505 | if (PRINTF_ABS(factor_exp2) > PRINTF_ABS(extra_factor_exp2)) { 506 | result.multiply = false; 507 | result.raw_factor = sf.raw_factor / extra_multiplicative_factor; 508 | } 509 | else { 510 | result.multiply = true; 511 | result.raw_factor = extra_multiplicative_factor / sf.raw_factor; 512 | } 513 | } 514 | return result; 515 | } 516 | 517 | #if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS 518 | static struct double_components get_normalized_components(bool negative, unsigned int precision, double non_normalized, struct scaling_factor normalization) 519 | { 520 | struct double_components components; 521 | components.is_negative = negative; 522 | components.integral = (int_fast64_t) apply_scaling(non_normalized, normalization); 523 | double remainder = non_normalized - unapply_scaling((double) components.integral, normalization); 524 | double prec_power_of_10 = powers_of_10[precision]; 525 | struct scaling_factor account_for_precision = update_normalization(normalization, prec_power_of_10); 526 | double scaled_remainder = apply_scaling(remainder, account_for_precision); 527 | double rounding_threshold = 0.5; 528 | 529 | if (precision == 0U) { 530 | components.fractional = 0; 531 | components.integral += (scaled_remainder >= rounding_threshold); 532 | if (scaled_remainder == rounding_threshold) { 533 | // banker's rounding: Round towards the even number (making the mean error 0) 534 | components.integral &= ~((int_fast64_t) 0x1); 535 | } 536 | } 537 | else { 538 | components.fractional = (int_fast64_t) scaled_remainder; 539 | scaled_remainder -= components.fractional; 540 | 541 | components.fractional += (scaled_remainder >= rounding_threshold); 542 | if (scaled_remainder == rounding_threshold) { 543 | // banker's rounding: Round towards the even number (making the mean error 0) 544 | components.fractional &= ~((int_fast64_t) 0x1); 545 | } 546 | // handle rollover, e.g. the case of 0.99 with precision 1 becoming (0,100), 547 | // and must then be corrected into (1, 0). 548 | if ((double) components.fractional >= prec_power_of_10) { 549 | components.fractional = 0; 550 | ++components.integral; 551 | } 552 | } 553 | return components; 554 | } 555 | #endif 556 | 557 | static size_t print_broken_up_decimal( 558 | struct double_components number_, out_fct_type out, char *buffer, size_t idx, size_t maxlen, unsigned int precision, 559 | unsigned int width, unsigned int flags, char *buf, size_t len) 560 | { 561 | if (precision != 0U) { 562 | // do fractional part, as an unsigned number 563 | 564 | unsigned int count = precision; 565 | 566 | if (flags & FLAGS_ADAPT_EXP && !(flags & FLAGS_HASH)) { 567 | // %g/%G mandates we skip the trailing 0 digits... 568 | if (number_.fractional > 0) { 569 | while(true) { 570 | int_fast64_t digit = number_.fractional % 10U; 571 | if (digit != 0) { 572 | break; 573 | } 574 | --count; 575 | number_.fractional /= 10U; 576 | } 577 | 578 | } 579 | // ... and even the decimal point if there are no 580 | // non-zero fractional part digits (see below) 581 | } 582 | 583 | if (number_.fractional > 0 || !(flags & FLAGS_ADAPT_EXP) || (flags & FLAGS_HASH) ) { 584 | while (len < PRINTF_FTOA_BUFFER_SIZE) { 585 | --count; 586 | buf[len++] = (char)('0' + number_.fractional % 10U); 587 | if (!(number_.fractional /= 10U)) { 588 | break; 589 | } 590 | } 591 | // add extra 0s 592 | while ((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0U)) { 593 | buf[len++] = '0'; 594 | } 595 | if (len < PRINTF_FTOA_BUFFER_SIZE) { 596 | buf[len++] = '.'; 597 | } 598 | } 599 | } 600 | else { 601 | if (flags & FLAGS_HASH) { 602 | if (len < PRINTF_FTOA_BUFFER_SIZE) { 603 | buf[len++] = '.'; 604 | } 605 | } 606 | } 607 | 608 | // Write the integer part of the number (it comes after the fractional 609 | // since the character order is reversed) 610 | while (len < PRINTF_FTOA_BUFFER_SIZE) { 611 | buf[len++] = (char)('0' + (number_.integral % 10)); 612 | if (!(number_.integral /= 10)) { 613 | break; 614 | } 615 | } 616 | 617 | // pad leading zeros 618 | if (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) { 619 | if (width && (number_.is_negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { 620 | width--; 621 | } 622 | while ((len < width) && (len < PRINTF_FTOA_BUFFER_SIZE)) { 623 | buf[len++] = '0'; 624 | } 625 | } 626 | 627 | if (len < PRINTF_FTOA_BUFFER_SIZE) { 628 | if (number_.is_negative) { 629 | buf[len++] = '-'; 630 | } 631 | else if (flags & FLAGS_PLUS) { 632 | buf[len++] = '+'; // ignore the space if the '+' exists 633 | } 634 | else if (flags & FLAGS_SPACE) { 635 | buf[len++] = ' '; 636 | } 637 | } 638 | 639 | return out_rev_(out, buffer, idx, maxlen, buf, len, width, flags); 640 | } 641 | 642 | // internal ftoa for fixed decimal floating point 643 | static size_t print_decimal_number(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double number, unsigned int precision, unsigned int width, unsigned int flags, char* buf, size_t len) 644 | { 645 | struct double_components value_ = get_components(number, precision); 646 | return print_broken_up_decimal(value_, out, buffer, idx, maxlen, precision, width, flags, buf, len); 647 | } 648 | 649 | #if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS 650 | // internal ftoa variant for exponential floating-point type, contributed by Martijn Jasperse 651 | static size_t print_exponential_number(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double number, unsigned int precision, unsigned int width, unsigned int flags, char* buf, size_t len) 652 | { 653 | const bool negative = get_sign(number); 654 | // This number will decrease gradually (by factors of 10) as we "extract" the exponent out of it 655 | double abs_number = negative ? -number : number; 656 | 657 | int exp10; 658 | bool abs_exp10_covered_by_powers_table; 659 | struct scaling_factor normalization; 660 | 661 | 662 | // Determine the decimal exponent 663 | if (abs_number == 0.0) { 664 | // TODO: This is a special-case for 0.0 (and -0.0); but proper handling is required for denormals more generally. 665 | exp10 = 0; // ... and no need to set a normalization factor or check the powers table 666 | } 667 | else { 668 | double_with_bit_access conv = get_bit_access(abs_number); 669 | { 670 | // based on the algorithm by David Gay (https://www.ampl.com/netlib/fp/dtoa.c) 671 | int exp2 = get_exp2(conv); 672 | // drop the exponent, so conv.F comes into the range [1,2) 673 | conv.U = (conv.U & (( (double_uint_t)(1) << DOUBLE_STORED_MANTISSA_BITS) - 1U)) | ((double_uint_t) DOUBLE_BASE_EXPONENT << DOUBLE_STORED_MANTISSA_BITS); 674 | // now approximate log10 from the log2 integer part and an expansion of ln around 1.5 675 | exp10 = (int)(0.1760912590558 + exp2 * 0.301029995663981 + (conv.F - 1.5) * 0.289529654602168); 676 | // now we want to compute 10^exp10 but we want to be sure it won't overflow 677 | exp2 = (int)(exp10 * 3.321928094887362 + 0.5); 678 | const double z = exp10 * 2.302585092994046 - exp2 * 0.6931471805599453; 679 | const double z2 = z * z; 680 | conv.U = ((double_uint_t)(exp2) + DOUBLE_BASE_EXPONENT) << DOUBLE_STORED_MANTISSA_BITS; 681 | // compute exp(z) using continued fractions, see https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex 682 | conv.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14))))); 683 | // correct for rounding errors 684 | if (abs_number < conv.F) { 685 | exp10--; 686 | conv.F /= 10; 687 | } 688 | } 689 | abs_exp10_covered_by_powers_table = PRINTF_ABS(exp10) < PRINTF_MAX_PRECOMPUTED_POWER_OF_10; 690 | normalization.raw_factor = abs_exp10_covered_by_powers_table ? powers_of_10[PRINTF_ABS(exp10)] : conv.F; 691 | } 692 | 693 | // We now begin accounting for the widths of the two parts of our printed field: 694 | // the decimal part after decimal exponent extraction, and the base-10 exponent part. 695 | // For both of these, the value of 0 has a special meaning, but not the same one: 696 | // a 0 exponent-part width means "don't print the exponent"; a 0 decimal-part width 697 | // means "use as many characters as necessary". 698 | 699 | bool fall_back_to_decimal_only_mode = false; 700 | if (flags & FLAGS_ADAPT_EXP) { 701 | int required_significant_digits = (precision == 0) ? 1 : (int) precision; 702 | // Should we want to fall-back to "%f" mode, and only print the decimal part? 703 | fall_back_to_decimal_only_mode = (exp10 >= -4 && exp10 < required_significant_digits); 704 | // Now, let's adjust the precision 705 | // This also decided how we adjust the precision value - as in "%g" mode, 706 | // "precision" is the number of _significant digits_, and this is when we "translate" 707 | // the precision value to an actual number of decimal digits. 708 | int precision_ = (fall_back_to_decimal_only_mode) ? 709 | (int) precision - 1 - exp10 : 710 | (int) precision - 1; // the presence of the exponent ensures only one significant digit comes before the decimal point 711 | precision = (precision_ > 0 ? (unsigned) precision_ : 0U); 712 | flags |= FLAGS_PRECISION; // make sure print_broken_up_decimal respects our choice above 713 | } 714 | 715 | normalization.multiply = (exp10 < 0 && abs_exp10_covered_by_powers_table); 716 | bool should_skip_normalization = (fall_back_to_decimal_only_mode || exp10 == 0); 717 | struct double_components decimal_part_components = 718 | should_skip_normalization ? 719 | get_components(negative ? -abs_number : abs_number, precision) : 720 | get_normalized_components(negative, precision, abs_number, normalization); 721 | 722 | // Account for roll-over, e.g. rounding from 9.99 to 100.0 - which effects 723 | // the exponent and may require additional tweaking of the parts 724 | if (fall_back_to_decimal_only_mode) { 725 | if ( (flags & FLAGS_ADAPT_EXP) && exp10 >= -1 && decimal_part_components.integral == powers_of_10[exp10 + 1]) { 726 | exp10++; // Not strictly necessary, since exp10 is no longer really used 727 | precision--; 728 | // ... and it should already be the case that decimal_part_components.fractional == 0 729 | } 730 | // TODO: What about rollover strictly within the fractional part? 731 | } 732 | else { 733 | if (decimal_part_components.integral >= 10) { 734 | exp10++; 735 | decimal_part_components.integral = 1; 736 | decimal_part_components.fractional = 0; 737 | } 738 | } 739 | 740 | // the exp10 format is "E%+03d" and largest possible exp10 value for a 64-bit double 741 | // is "307" (for 2^1023), so we set aside 4-5 characters overall 742 | unsigned int exp10_part_width = fall_back_to_decimal_only_mode ? 0U : (PRINTF_ABS(exp10) < 100) ? 4U : 5U; 743 | 744 | unsigned int decimal_part_width = 745 | ((flags & FLAGS_LEFT) && exp10_part_width) ? 746 | // We're padding on the right, so the width constraint is the exponent part's 747 | // problem, not the decimal part's, so we'll use as many characters as we need: 748 | 0U : 749 | // We're padding on the left; so the width constraint is the decimal part's 750 | // problem. Well, can both the decimal part and the exponent part fit within our overall width? 751 | ((width > exp10_part_width) ? 752 | // Yes, so we limit our decimal part's width. 753 | // (Note this is trivially valid even if we've fallen back to "%f" mode) 754 | width - exp10_part_width : 755 | // No; we just give up on any restriction on the decimal part and use as many 756 | // characters as we need 757 | 0U); 758 | 759 | const size_t start_idx = idx; 760 | idx = print_broken_up_decimal(decimal_part_components, out, buffer, idx, maxlen, precision, decimal_part_width, flags, buf, len); 761 | 762 | if (! fall_back_to_decimal_only_mode) { 763 | out((flags & FLAGS_UPPERCASE) ? 'E' : 'e', buffer, idx++, maxlen); 764 | idx = print_integer(out, buffer, idx, maxlen, 765 | ABS_FOR_PRINTING(exp10), 766 | exp10 < 0, 10, 0, exp10_part_width - 1, 767 | FLAGS_ZEROPAD | FLAGS_PLUS); 768 | if (flags & FLAGS_LEFT) { 769 | // We need to right-pad with spaces to meet the width requirement 770 | while (idx - start_idx < width) out(' ', buffer, idx++, maxlen); 771 | } 772 | } 773 | return idx; 774 | } 775 | #endif // PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS 776 | 777 | 778 | static size_t print_floating_point(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int precision, unsigned int width, unsigned int flags, bool prefer_exponential) 779 | { 780 | char buf[PRINTF_FTOA_BUFFER_SIZE]; 781 | size_t len = 0U; 782 | 783 | // test for special values 784 | if (value != value) 785 | return out_rev_(out, buffer, idx, maxlen, "nan", 3, width, flags); 786 | if (value < -DBL_MAX) 787 | return out_rev_(out, buffer, idx, maxlen, "fni-", 4, width, flags); 788 | if (value > DBL_MAX) 789 | return out_rev_(out, buffer, idx, maxlen, (flags & FLAGS_PLUS) ? "fni+" : "fni", (flags & FLAGS_PLUS) ? 4U : 3U, width, flags); 790 | 791 | if (!prefer_exponential && ((value > PRINTF_FLOAT_NOTATION_THRESHOLD) || (value < -PRINTF_FLOAT_NOTATION_THRESHOLD))) { 792 | // The required behavior of standard printf is to print _every_ integral-part digit -- which could mean 793 | // printing hundreds of characters, overflowing any fixed internal buffer and necessitating a more complicated 794 | // implementation. 795 | #if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS 796 | return print_exponential_number(out, buffer, idx, maxlen, value, precision, width, flags, buf, len); 797 | #else 798 | return 0U; 799 | #endif 800 | } 801 | 802 | // set default precision, if not set explicitly 803 | if (!(flags & FLAGS_PRECISION)) { 804 | precision = PRINTF_DEFAULT_FLOAT_PRECISION; 805 | } 806 | 807 | // limit precision so that our integer holding the fractional part does not overflow 808 | while ((len < PRINTF_FTOA_BUFFER_SIZE) && (precision > PRINTF_MAX_SUPPORTED_PRECISION)) { 809 | buf[len++] = '0'; // This respects the precision in terms of result length only 810 | precision--; 811 | } 812 | 813 | return 814 | #if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS 815 | prefer_exponential ? 816 | print_exponential_number(out, buffer, idx, maxlen, value, precision, width, flags, buf, len) : 817 | #endif 818 | print_decimal_number(out, buffer, idx, maxlen, value, precision, width, flags, buf, len); 819 | } 820 | 821 | #endif // (PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS) 822 | 823 | // internal vsnprintf 824 | static int _vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const char* format, va_list va) 825 | { 826 | unsigned int flags, width, precision, n; 827 | size_t idx = 0U; 828 | 829 | if (!buffer) { 830 | // use null output function 831 | out = out_discard; 832 | } 833 | 834 | while (*format) 835 | { 836 | // format specifier? %[flags][width][.precision][length] 837 | if (*format != '%') { 838 | // no 839 | out(*format, buffer, idx++, maxlen); 840 | format++; 841 | continue; 842 | } 843 | else { 844 | // yes, evaluate it 845 | format++; 846 | } 847 | 848 | // evaluate flags 849 | flags = 0U; 850 | do { 851 | switch (*format) { 852 | case '0': flags |= FLAGS_ZEROPAD; format++; n = 1U; break; 853 | case '-': flags |= FLAGS_LEFT; format++; n = 1U; break; 854 | case '+': flags |= FLAGS_PLUS; format++; n = 1U; break; 855 | case ' ': flags |= FLAGS_SPACE; format++; n = 1U; break; 856 | case '#': flags |= FLAGS_HASH; format++; n = 1U; break; 857 | default : n = 0U; break; 858 | } 859 | } while (n); 860 | 861 | // evaluate width field 862 | width = 0U; 863 | if (is_digit_(*format)) { 864 | width = atoi_(&format); 865 | } 866 | else if (*format == '*') { 867 | const int w = va_arg(va, int); 868 | if (w < 0) { 869 | flags |= FLAGS_LEFT; // reverse padding 870 | width = (unsigned int)-w; 871 | } 872 | else { 873 | width = (unsigned int)w; 874 | } 875 | format++; 876 | } 877 | 878 | // evaluate precision field 879 | precision = 0U; 880 | if (*format == '.') { 881 | flags |= FLAGS_PRECISION; 882 | format++; 883 | if (is_digit_(*format)) { 884 | precision = atoi_(&format); 885 | } 886 | else if (*format == '*') { 887 | const int precision_ = (int)va_arg(va, int); 888 | precision = precision_ > 0 ? (unsigned int)precision_ : 0U; 889 | format++; 890 | } 891 | } 892 | 893 | // evaluate length field 894 | switch (*format) { 895 | case 'l' : 896 | flags |= FLAGS_LONG; 897 | format++; 898 | if (*format == 'l') { 899 | flags |= FLAGS_LONG_LONG; 900 | format++; 901 | } 902 | break; 903 | case 'h' : 904 | flags |= FLAGS_SHORT; 905 | format++; 906 | if (*format == 'h') { 907 | flags |= FLAGS_CHAR; 908 | format++; 909 | } 910 | break; 911 | case 't' : 912 | flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); 913 | format++; 914 | break; 915 | case 'j' : 916 | flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); 917 | format++; 918 | break; 919 | case 'z' : 920 | flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); 921 | format++; 922 | break; 923 | default: 924 | break; 925 | } 926 | 927 | // evaluate specifier 928 | switch (*format) { 929 | case 'd' : 930 | case 'i' : 931 | case 'u' : 932 | case 'x' : 933 | case 'X' : 934 | case 'o' : 935 | case 'b' : { 936 | // set the base 937 | numeric_base_t base; 938 | if (*format == 'x' || *format == 'X') { 939 | base = BASE_HEX; 940 | } 941 | else if (*format == 'o') { 942 | base = BASE_OCTAL; 943 | } 944 | else if (*format == 'b') { 945 | base = BASE_BINARY; 946 | } 947 | else { 948 | base = BASE_DECIMAL; 949 | flags &= ~FLAGS_HASH; // no hash for dec format 950 | } 951 | // uppercase 952 | if (*format == 'X') { 953 | flags |= FLAGS_UPPERCASE; 954 | } 955 | 956 | // no plus or space flag for u, x, X, o, b 957 | if ((*format != 'i') && (*format != 'd')) { 958 | flags &= ~(FLAGS_PLUS | FLAGS_SPACE); 959 | } 960 | 961 | // ignore '0' flag when precision is given 962 | if (flags & FLAGS_PRECISION) { 963 | flags &= ~FLAGS_ZEROPAD; 964 | } 965 | 966 | // convert the integer 967 | if ((*format == 'i') || (*format == 'd')) { 968 | // signed 969 | if (flags & FLAGS_LONG_LONG) { 970 | #if PRINTF_SUPPORT_LONG_LONG 971 | const long long value = va_arg(va, long long); 972 | idx = print_integer(out, buffer, idx, maxlen, ABS_FOR_PRINTING(value), value < 0, base, precision, width, flags); 973 | #endif 974 | } 975 | else if (flags & FLAGS_LONG) { 976 | const long value = va_arg(va, long); 977 | idx = print_integer(out, buffer, idx, maxlen, ABS_FOR_PRINTING(value), value < 0, base, precision, width, flags); 978 | } 979 | else { 980 | const int value = (flags & FLAGS_CHAR) ? (signed char)va_arg(va, int) : (flags & FLAGS_SHORT) ? (short int)va_arg(va, int) : va_arg(va, int); 981 | idx = print_integer(out, buffer, idx, maxlen, ABS_FOR_PRINTING(value), value < 0, base, precision, width, flags); 982 | } 983 | } 984 | else { 985 | // unsigned 986 | if (flags & FLAGS_LONG_LONG) { 987 | #if PRINTF_SUPPORT_LONG_LONG 988 | idx = print_integer(out, buffer, idx, maxlen, (printf_unsigned_value_t) va_arg(va, unsigned long long), false, base, precision, width, flags); 989 | #endif 990 | } 991 | else if (flags & FLAGS_LONG) { 992 | idx = print_integer(out, buffer, idx, maxlen, (printf_unsigned_value_t) va_arg(va, unsigned long), false, base, precision, width, flags); 993 | } 994 | else { 995 | const unsigned int value = (flags & FLAGS_CHAR) ? (unsigned char)va_arg(va, unsigned int) : (flags & FLAGS_SHORT) ? (unsigned short int)va_arg(va, unsigned int) : va_arg(va, unsigned int); 996 | idx = print_integer(out, buffer, idx, maxlen, (printf_unsigned_value_t) value, false, base, precision, width, flags); 997 | } 998 | } 999 | format++; 1000 | break; 1001 | } 1002 | #if PRINTF_SUPPORT_DECIMAL_SPECIFIERS 1003 | case 'f' : 1004 | case 'F' : 1005 | if (*format == 'F') flags |= FLAGS_UPPERCASE; 1006 | idx = print_floating_point(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags, PRINTF_PREFER_DECIMAL); 1007 | format++; 1008 | break; 1009 | #endif 1010 | #if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS 1011 | case 'e': 1012 | case 'E': 1013 | case 'g': 1014 | case 'G': 1015 | if ((*format == 'g')||(*format == 'G')) flags |= FLAGS_ADAPT_EXP; 1016 | if ((*format == 'E')||(*format == 'G')) flags |= FLAGS_UPPERCASE; 1017 | idx = print_floating_point(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags, PRINTF_PREFER_EXPONENTIAL); 1018 | format++; 1019 | break; 1020 | #endif // PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS 1021 | case 'c' : { 1022 | unsigned int l = 1U; 1023 | // pre padding 1024 | if (!(flags & FLAGS_LEFT)) { 1025 | while (l++ < width) { 1026 | out(' ', buffer, idx++, maxlen); 1027 | } 1028 | } 1029 | // char output 1030 | out((char)va_arg(va, int), buffer, idx++, maxlen); 1031 | // post padding 1032 | if (flags & FLAGS_LEFT) { 1033 | while (l++ < width) { 1034 | out(' ', buffer, idx++, maxlen); 1035 | } 1036 | } 1037 | format++; 1038 | break; 1039 | } 1040 | 1041 | case 's' : { 1042 | const char* p = va_arg(va, char*); 1043 | if (p == NULL) { 1044 | idx = out_rev_(out, buffer, idx, maxlen, ")llun(", 6, width, flags); 1045 | } 1046 | else { 1047 | unsigned int l = strnlen_s_(p, precision ? precision : (size_t)-1); 1048 | // pre padding 1049 | if (flags & FLAGS_PRECISION) { 1050 | l = (l < precision ? l : precision); 1051 | } 1052 | if (!(flags & FLAGS_LEFT)) { 1053 | while (l++ < width) { 1054 | out(' ', buffer, idx++, maxlen); 1055 | } 1056 | } 1057 | // string output 1058 | while ((*p != 0) && (!(flags & FLAGS_PRECISION) || precision--)) { 1059 | out(*(p++), buffer, idx++, maxlen); 1060 | } 1061 | // post padding 1062 | if (flags & FLAGS_LEFT) { 1063 | while (l++ < width) { 1064 | out(' ', buffer, idx++, maxlen); 1065 | } 1066 | } 1067 | } 1068 | format++; 1069 | break; 1070 | } 1071 | 1072 | case 'p' : { 1073 | width = sizeof(void*) * 2U + 2; // 2 hex chars per byte + the "0x" prefix 1074 | flags |= FLAGS_ZEROPAD | FLAGS_POINTER; 1075 | uintptr_t value = (uintptr_t)va_arg(va, void*); 1076 | idx = (value == (uintptr_t) NULL) ? 1077 | out_rev_(out, buffer, idx, maxlen, ")lin(", 5, width, flags) : 1078 | print_integer(out, buffer, idx, maxlen, (printf_unsigned_value_t) value, false, BASE_HEX, precision, width, flags); 1079 | format++; 1080 | break; 1081 | } 1082 | 1083 | case '%' : 1084 | out('%', buffer, idx++, maxlen); 1085 | format++; 1086 | break; 1087 | 1088 | // Many people prefer to disable support for %n, as it lets the caller 1089 | // engineer a write to an arbitrary location, of a value the caller 1090 | // effectively controls - which could be a security concern in some cases. 1091 | #if PRINTF_SUPPORT_WRITEBACK_SPECIFIER 1092 | case 'n' : { 1093 | if (flags & FLAGS_CHAR) *(va_arg(va, char*)) = (char) idx; 1094 | else if (flags & FLAGS_SHORT) *(va_arg(va, short*)) = (short) idx; 1095 | else if (flags & FLAGS_LONG) *(va_arg(va, long*)) = (long) idx; 1096 | #if PRINTF_SUPPORT_LONG_LONG 1097 | else if (flags & FLAGS_LONG_LONG) *(va_arg(va, long long*)) = (long long int) idx; 1098 | #endif // PRINTF_SUPPORT_LONG_LONG 1099 | else *(va_arg(va, int*)) = (int) idx; 1100 | format++; 1101 | break; 1102 | } 1103 | #endif // PRINTF_SUPPORT_WRITEBACK_SPECIFIER 1104 | 1105 | default : 1106 | out(*format, buffer, idx++, maxlen); 1107 | format++; 1108 | break; 1109 | } 1110 | } 1111 | 1112 | // termination 1113 | out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen); 1114 | 1115 | // return written chars without terminating \0 1116 | return (int)idx; 1117 | } 1118 | 1119 | 1120 | /////////////////////////////////////////////////////////////////////////////// 1121 | 1122 | int printf_(const char* format, ...) 1123 | { 1124 | va_list va; 1125 | va_start(va, format); 1126 | char buffer[1]; 1127 | const int ret = _vsnprintf(out_putchar, buffer, (size_t)-1, format, va); 1128 | va_end(va); 1129 | return ret; 1130 | } 1131 | 1132 | 1133 | int sprintf_(char* buffer, const char* format, ...) 1134 | { 1135 | va_list va; 1136 | va_start(va, format); 1137 | const int ret = _vsnprintf(out_buffer, buffer, (size_t)-1, format, va); 1138 | va_end(va); 1139 | return ret; 1140 | } 1141 | 1142 | 1143 | int snprintf_(char* buffer, size_t count, const char* format, ...) 1144 | { 1145 | va_list va; 1146 | va_start(va, format); 1147 | const int ret = _vsnprintf(out_buffer, buffer, count, format, va); 1148 | va_end(va); 1149 | return ret; 1150 | } 1151 | 1152 | 1153 | int vprintf_(const char* format, va_list va) 1154 | { 1155 | char buffer[1]; 1156 | return _vsnprintf(out_putchar, buffer, (size_t)-1, format, va); 1157 | } 1158 | 1159 | int vsprintf_(char* buffer, const char* format, va_list va) 1160 | { 1161 | return _vsnprintf(out_buffer, buffer, (size_t)-1, format, va); 1162 | } 1163 | 1164 | int vsnprintf_(char* buffer, size_t count, const char* format, va_list va) 1165 | { 1166 | return _vsnprintf(out_buffer, buffer, count, format, va); 1167 | } 1168 | 1169 | 1170 | int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...) 1171 | { 1172 | va_list va; 1173 | va_start(va, format); 1174 | const int ret = vfctprintf(out, arg, format, va); 1175 | va_end(va); 1176 | return ret; 1177 | } 1178 | 1179 | int vfctprintf(void (*out)(char character, void* arg), void* arg, const char* format, va_list va) 1180 | { 1181 | const out_function_wrapper_type out_fct_wrap = { out, arg }; 1182 | return _vsnprintf(out_wrapped_function, (char*)(uintptr_t)&out_fct_wrap, (size_t)-1, format, va); 1183 | } 1184 | 1185 | -------------------------------------------------------------------------------- /extras/printf/printf.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @author (c) Eyal Rozenberg 3 | * 2021, Haifa, Palestine/Israel 4 | * @author (c) Marco Paland (info@paland.com) 5 | * 2014-2019, PALANDesign Hannover, Germany 6 | * 7 | * @note Others have made smaller contributions to this file: see the 8 | * contributors page at https://github.com/eyalroz/printf/graphs/contributors 9 | * or ask one of the authors. 10 | * 11 | * @brief Small stand-alone implementation of the printf family of functions 12 | * (`(v)printf`, `(v)s(n)printf` etc., geared towards use on embedded systems with 13 | * a very limited resources. 14 | * 15 | * @note the implementations are thread-safe; re-entrant; use no functions from 16 | * the standard library; and do not dynamically allocate any memory. 17 | * 18 | * @license The MIT License (MIT) 19 | * 20 | * Permission is hereby granted, free of charge, to any person obtaining a copy 21 | * of this software and associated documentation files (the "Software"), to deal 22 | * in the Software without restriction, including without limitation the rights 23 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 24 | * copies of the Software, and to permit persons to whom the Software is 25 | * furnished to do so, subject to the following conditions: 26 | * 27 | * The above copyright notice and this permission notice shall be included in 28 | * all copies or substantial portions of the Software. 29 | * 30 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 31 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 32 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 33 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 34 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 35 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 36 | * THE SOFTWARE. 37 | */ 38 | 39 | #ifndef PRINTF_H_ 40 | #define PRINTF_H_ 41 | 42 | #include 43 | #include 44 | 45 | 46 | #ifdef __cplusplus 47 | extern "C" { 48 | #endif 49 | 50 | #ifdef __GNUC__ 51 | # define ATTR_PRINTF(one_based_format_index, first_arg) \ 52 | __attribute__((format(__printf__, (one_based_format_index), (first_arg)))) 53 | # define ATTR_VPRINTF(one_based_format_index) ATTR_PRINTF(one_based_format_index, 0) 54 | #else 55 | # define ATTR_PRINTF(one_based_format_index, first_arg) 56 | # define ATTR_VPRINTF(one_based_format_index) 57 | #endif 58 | 59 | #ifndef PRINTF_ALIAS_STANDARD_FUNCTION_NAMES 60 | #define PRINTF_ALIAS_STANDARD_FUNCTION_NAMES 0 61 | #endif 62 | 63 | #if PRINTF_ALIAS_STANDARD_FUNCTION_NAMES 64 | # define printf_ printf 65 | # define sprintf_ sprintf 66 | # define vsprintf_ vsprintf 67 | # define snprintf_ snprintf 68 | # define vsnprintf_ vsnprintf 69 | # define vprintf_ vprintf 70 | #endif 71 | 72 | /** 73 | * Output a character to a custom device like UART, used by the printf() function 74 | * This function is declared here only. You have to write your custom implementation somewhere 75 | * @param character Character to output 76 | */ 77 | void putchar_(char character); 78 | 79 | 80 | /** 81 | * Tiny printf implementation 82 | * You have to implement putchar_ if you use printf() 83 | * To avoid conflicts with the regular printf() API it is overridden by macro defines 84 | * and internal underscore-appended functions like printf_() are used 85 | * @param format A string that specifies the format of the output 86 | * @return The number of characters that are written into the array, not counting the terminating null character 87 | */ 88 | int printf_(const char* format, ...) ATTR_PRINTF(1, 2); 89 | 90 | 91 | /** 92 | * Tiny sprintf/vsprintf implementation 93 | * Due to security reasons (buffer overflow) YOU SHOULD CONSIDER USING (V)SNPRINTF INSTEAD! 94 | * @param buffer A pointer to the buffer where to store the formatted string. MUST be big enough to store the output! 95 | * @param format A string that specifies the format of the output 96 | * @param va A value identifying a variable arguments list 97 | * @return The number of characters that are WRITTEN into the buffer, not counting the terminating null character 98 | */ 99 | int sprintf_(char* buffer, const char* format, ...) ATTR_PRINTF(2, 3); 100 | int vsprintf_(char* buffer, const char* format, va_list va) ATTR_VPRINTF(2); 101 | 102 | 103 | /** 104 | * Tiny snprintf/vsnprintf implementation 105 | * @param buffer A pointer to the buffer where to store the formatted string 106 | * @param count The maximum number of characters to store in the buffer, including a terminating null character 107 | * @param format A string that specifies the format of the output 108 | * @param va A value identifying a variable arguments list 109 | * @return The number of characters that COULD have been written into the buffer, not counting the terminating 110 | * null character. A value equal or larger than count indicates truncation. Only when the returned value 111 | * is non-negative and less than count, the string has been completely written. 112 | */ 113 | int snprintf_(char* buffer, size_t count, const char* format, ...) ATTR_PRINTF(3, 4); 114 | int vsnprintf_(char* buffer, size_t count, const char* format, va_list va) ATTR_VPRINTF(3); 115 | 116 | 117 | /** 118 | * Tiny vprintf implementation 119 | * @param format A string that specifies the format of the output 120 | * @param va A value identifying a variable arguments list 121 | * @return The number of characters that are WRITTEN into the buffer, not counting the terminating null character 122 | */ 123 | int vprintf_(const char* format, va_list va) ATTR_VPRINTF(1); 124 | 125 | 126 | /** 127 | * printf/vprintf with output function 128 | * You may use this as dynamic alternative to printf() with its fixed putchar_() output 129 | * @param out An output function which takes one character and an argument pointer 130 | * @param arg An argument pointer for user data passed to output function 131 | * @param format A string that specifies the format of the output 132 | * @param va A value identifying a variable arguments list 133 | * @return The number of characters that are sent to the output function, not counting the terminating null character 134 | */ 135 | int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...) ATTR_PRINTF(3, 4); 136 | int vfctprintf(void (*out)(char character, void* arg), void* arg, const char* format, va_list va) ATTR_VPRINTF(3); 137 | 138 | #ifdef __cplusplus 139 | } 140 | #endif 141 | 142 | #if PRINTF_ALIAS_STANDARD_FUNCTION_NAMES 143 | # undef printf_ 144 | # undef sprintf_ 145 | # undef vsprintf_ 146 | # undef snprintf_ 147 | # undef vsnprintf_ 148 | # undef vprintf_ 149 | #endif 150 | 151 | #endif // PRINTF_H_ 152 | -------------------------------------------------------------------------------- /extras/sketch_cpp/default_to_serial.cpp: -------------------------------------------------------------------------------- 1 | #include "Arduino.h" 2 | #include "../../examples/default_to_serial/default_to_serial.ino" 3 | -------------------------------------------------------------------------------- /extras/sketch_cpp/override_putchar.cpp: -------------------------------------------------------------------------------- 1 | #include "Arduino.h" 2 | #include "../../examples/override_putchar/override_putchar.ino" 3 | -------------------------------------------------------------------------------- /extras/sketch_cpp/specify_print_class.cpp: -------------------------------------------------------------------------------- 1 | #include "Arduino.h" 2 | #include "../../examples/specify_print_class/specify_print_class.ino" 3 | -------------------------------------------------------------------------------- /extras/test/LibPrintf.h: -------------------------------------------------------------------------------- 1 | #define PRINTF_ALIAS_STANDARD_FUNCTION_NAMES 1 2 | #include "printf.h" 3 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "LibPrintf", 3 | "version": "1.2.13", 4 | "description": "This library provides support for printf() and other printf-like functions with full format-string support. Default output is to Serial, but can be customized.", 5 | "keywords": "printf, debugging", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/embeddedartistry/arduino-printf.git" 9 | }, 10 | "license": "MIT", 11 | "headers": "LibPrintf.h", 12 | "frameworks": "arduino", 13 | "platforms": "*", 14 | "export": { 15 | "include": [ 16 | "examples", 17 | "src", 18 | "extras/printf" 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=LibPrintf 2 | version=1.2.13 3 | author=Embedded Artistry 4 | maintainer=Embedded Artistry 5 | sentence=Library adding support for the printf family of functions to the Arduino SDK. 6 | paragraph=This library provides support for printf() and other printf-like functions with full format-string support. Default output is to Serial, but can be customized. 7 | category=Communication 8 | url=https://github.com/embeddedartistry/arduino-printf 9 | architectures=* 10 | includes=LibPrintf.h 11 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('Arduino-Printf', 'cpp', 2 | version : '1.1.0', 3 | license : 'MIT', 4 | default_options : ['buildtype=minsize', 5 | 'b_lto=true', 6 | 'cpp_std=gnu++11', 7 | 'b_staticpic=false', 8 | ], 9 | ) 10 | 11 | # This dependency is used when using libPrintf in unit tests on the host machine 12 | libPrintf_test_dep = declare_dependency( 13 | include_directories: include_directories('extras/printf', 'extras/test', is_system: true), 14 | sources: files('extras/printf/printf.c'), 15 | ) 16 | 17 | # The library itself is only available when cross-compiled 18 | # for AVR due to the use of Arduino Core 19 | if meson.is_cross_build() 20 | if host_machine.cpu_family() != 'avr' 21 | error('The Meson build for this library is only configured for AVR processors') 22 | endif 23 | 24 | arduinocore = subproject('arduinocore-avr') 25 | arduinocore_dep = arduinocore.get_variable('arduinocore_dep') 26 | arduinocore_main_dep = arduinocore.get_variable('arduinocore_main_dep') 27 | 28 | ################## 29 | # Library Target # 30 | ################## 31 | libPrintf = static_library('Printf', 32 | 'src/LibPrintf.cpp', 33 | include_directories: include_directories('src'), 34 | dependencies: arduinocore_dep, 35 | build_by_default: meson.is_subproject() == false 36 | ) 37 | 38 | ####################### 39 | # External Dependency # 40 | ####################### 41 | libPrintf_dep = declare_dependency( 42 | include_directories: include_directories('src', is_system: true), 43 | link_with: libPrintf, 44 | ) 45 | 46 | ########################## 47 | # Example Sketch Targets # 48 | ########################## 49 | if meson.is_subproject() == false 50 | # Compile example applications 51 | executable('default_to_serial', 52 | files('extras/sketch_cpp/default_to_serial.cpp'), 53 | dependencies: [ 54 | libPrintf_dep, 55 | arduinocore_dep, 56 | arduinocore_main_dep, 57 | ], 58 | install: false 59 | ) 60 | 61 | executable('override_putchar', 62 | files('extras/sketch_cpp/override_putchar.cpp'), 63 | dependencies: [ 64 | libPrintf_dep, 65 | arduinocore_dep, 66 | arduinocore_main_dep, 67 | ], 68 | install: false 69 | ) 70 | 71 | if host_machine.cpu() == 'atmega2560' 72 | # Serial1 is only usable on the Mega, not the Uno 73 | executable('specify_print_class', 74 | files('extras/sketch_cpp/specify_print_class.cpp'), 75 | dependencies: [ 76 | libPrintf_dep, 77 | arduinocore_dep, 78 | arduinocore_main_dep, 79 | ], 80 | install: false 81 | ) 82 | endif 83 | endif 84 | endif 85 | -------------------------------------------------------------------------------- /src/LibPrintf.cpp: -------------------------------------------------------------------------------- 1 | #include "LibPrintf.h" 2 | 3 | #ifndef PRINTF_DISABLE_ALL 4 | #include "Arduino.h" 5 | 6 | #ifdef __AVR__ 7 | #ifndef PRINTF_PREVENT_DEFAULT_AVR_SETTINGS 8 | #define PRINTF_DISABLE_SUPPORT_EXPONENTIAL 9 | #define PRINTF_DISABLE_SUPPORT_LONG_LONG 10 | #endif 11 | #endif 12 | 13 | // We include `printf.c` hear so we can actually control the default settings without 14 | // modifying the original source file 15 | #include "../extras/printf/printf.c" 16 | 17 | static Print* print_instance = &Serial; 18 | 19 | void printf_init(Print& PrintClass) 20 | { 21 | print_instance = &PrintClass; 22 | } 23 | 24 | // If you use the default printf() implementation, this function will route the output 25 | // to the Serial class 26 | extern "C" __attribute__((weak)) void putchar_(char character) 27 | { 28 | print_instance->print(character); 29 | } 30 | 31 | #endif // PRINTF_DISABLE_ALL 32 | -------------------------------------------------------------------------------- /src/LibPrintf.h: -------------------------------------------------------------------------------- 1 | #ifndef ARDUINO_PRINTF_H_ 2 | #define ARDUINO_PRINTF_H_ 3 | 4 | #ifdef PRINTF_DISABLE_ALL 5 | 6 | #define printf(...) 7 | #define sprintf(...) 8 | #define vsprintf(...) 9 | #define snprintf(...) 10 | #define vsnprintf(...) 11 | #define vprintf(...) 12 | 13 | #define printf_init(...) 14 | 15 | #else // printf is enabled 16 | 17 | #include "Print.h" 18 | #include "../extras/printf/printf.h" 19 | 20 | # define printf printf_ 21 | # define sprintf sprintf_ 22 | # define vsprintf vsprintf_ 23 | # define snprintf snprintf_ 24 | # define vsnprintf vsnprintf_ 25 | # define vprintf vprintf_ 26 | 27 | // Adds a compatibility definition for those who were using the old library 28 | #define _putchar(c) putchar_(c) 29 | 30 | // In Setup(), you must initialize printf with a Print class if you don't want 31 | // to use the default Serial object. If you want the default behavior, calling this 32 | // function is not necessary. 33 | // 34 | // The caller is responsible for configure the Serial interface in setup() and calling 35 | // Serial.begin(). 36 | #ifdef __AVR_ATmega4809__ 37 | void printf_init(arduino::Print& StreamClass); 38 | #else 39 | void printf_init(Print& StreamClass); 40 | #endif // __AVR_ATmega4809__ 41 | 42 | #endif // PRINTF_DISABLE_ALL 43 | #endif //ARDUINO_PRINTF_H_ 44 | -------------------------------------------------------------------------------- /subprojects/arduinocore-avr.wrap: -------------------------------------------------------------------------------- 1 | [wrap-file] 2 | directory = avr 3 | 4 | source_url = http://downloads.arduino.cc/cores/avr-1.8.2.tar.bz2 5 | source_filename = avr-1.8.2.tar.bz2 6 | source_hash = 6213d41c6e91a75ac931527da5b10f2dbe0140c8cc1dd41b06cd4e78b943f41b 7 | 8 | patch_url = https://github.com/embeddedartistry/arduinocore-avr/releases/download/1.8.2-1/arduinocore-avr-1.8.2-1-patch.zip 9 | patch_filename = arduinocore-avr-1.8.2-1-patch.zip 10 | patch_hash = fcd198fc38aa55398d448a23fc006005331a4943b0c440f38b61b671c1324a25 11 | -------------------------------------------------------------------------------- /tools/CI.jenkinsfile: -------------------------------------------------------------------------------- 1 | #!groovy 2 | @Library('jenkins-pipeline-lib') _ 3 | 4 | pipeline 5 | { 6 | agent any 7 | stages 8 | { 9 | stage('Clean') 10 | { 11 | when 12 | { 13 | expression 14 | { 15 | /* 16 | * If the previous build suceeeded (unstable means test failed but build passed) 17 | * then we continue on in CI mode. If the previous build failed we want to 18 | * start with a clean environment. This is done to reduce manual user interation. 19 | */ 20 | return !(didLastBuildSucceed()) 21 | } 22 | } 23 | steps 24 | { 25 | echo('Previous build failed: Running a clean build.') 26 | sh 'make distclean' 27 | } 28 | } 29 | stage('Cross compile for AVR') 30 | { 31 | steps 32 | { 33 | sh 'make CROSS=avr:arduino_mega' 34 | } 35 | post 36 | { 37 | always 38 | { 39 | recordIssues( 40 | healthy: 5, 41 | unhealthy: 10, 42 | aggregatingResults: true, 43 | referenceJobName: 'ea-nightly/arduino-printf/master', 44 | sourceDirectory: 'buildresults/', 45 | filters: [ 46 | excludeFile('subprojects/*') 47 | ], 48 | qualityGates: [ 49 | // 3 new issue: unstable 50 | [threshold: 3, type: 'DELTA', unstable: true], 51 | // 5 new issues: failed build 52 | [threshold: 5, type: 'DELTA', unstable: false], 53 | // 10 total issues: unstable 54 | [threshold: 10, type: 'TOTAL', unstable: true], 55 | // 20 total issues: fail 56 | [threshold: 20, type: 'TOTAL', unstable: false] 57 | ], 58 | tools: [ 59 | gcc(id: 'gcc-avr', name: 'gcc-avr'), 60 | ] 61 | ) 62 | } 63 | } 64 | } 65 | } 66 | post 67 | { 68 | always 69 | { 70 | // Scan for open tasks, warnings, issues, etc. 71 | recordIssues( 72 | healthy: 5, 73 | unhealthy: 10, 74 | aggregatingResults: true, 75 | referenceJobName: 'ea-nightly/arduino-printf/master', 76 | filters: [ 77 | excludeFile('subprojects/*') 78 | ], 79 | qualityGates: [ 80 | // 3 new issue: unstable 81 | [threshold: 3, type: 'DELTA', unstable: true], 82 | // 5 new issues: failed build 83 | [threshold: 5, type: 'DELTA', unstable: false], 84 | // 10 total issues: unstable 85 | [threshold: 10, type: 'TOTAL', unstable: true], 86 | // 20 total issues: fail 87 | [threshold: 20, type: 'TOTAL', unstable: false] 88 | ], 89 | tools: [ 90 | taskScanner( 91 | excludePattern: 'buildresults/**, subprojects/**, build/**, extras/**', 92 | includePattern: '**/*.c, **/*.cpp, **/*.h, **/*.hpp, **/*.sh, **/*.build, **/*.ino', 93 | normalTags: 'TODO, to do, WIP', 94 | highTags: 'FIXME, FIX', 95 | ignoreCase: true, 96 | ), 97 | ] 98 | ) 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /tools/Jenkinsfile: -------------------------------------------------------------------------------- 1 | #!groovy 2 | @Library('jenkins-pipeline-lib') _ 3 | 4 | pipeline 5 | { 6 | agent any 7 | environment 8 | { 9 | GIT_CHANGE_LOG = gitChangeLog(currentBuild.changeSets) 10 | } 11 | parameters 12 | { 13 | string(defaultValue: '1', description: 'Major version number (x.0.0)', name: 'MAJOR_VERSION') 14 | string(defaultValue: '2', description: 'Minor version number (0.x.0)', name: 'MINOR_VERSION') 15 | } 16 | triggers 17 | { 18 | //At 04:00 on every day-of-week from Monday through Friday. 19 | pollSCM('H 4 * * 1-5') 20 | } 21 | stages 22 | { 23 | stage('Setup') 24 | { 25 | steps 26 | { 27 | gitTagPreBuild "${params.MAJOR_VERSION}.${params.MINOR_VERSION}.${BUILD_NUMBER}" 28 | 29 | echo 'Removing existing build results' 30 | sh 'make distclean' 31 | } 32 | } 33 | stage('Cross compile for AVR') 34 | { 35 | steps 36 | { 37 | sh 'make CROSS=avr:arduino_mega' 38 | } 39 | post 40 | { 41 | always 42 | { 43 | recordIssues( 44 | healthy: 5, 45 | unhealthy: 10, 46 | aggregatingResults: true, 47 | referenceJobName: 'ea-nightly/arduino-printf/master', 48 | sourceDirectory: 'buildresults/', 49 | filters: [ 50 | excludeFile('subprojects/*') 51 | ], 52 | qualityGates: [ 53 | // 3 new issue: unstable 54 | [threshold: 3, type: 'DELTA', unstable: true], 55 | // 5 new issues: failed build 56 | [threshold: 5, type: 'DELTA', unstable: false], 57 | // 10 total issues: unstable 58 | [threshold: 10, type: 'TOTAL', unstable: true], 59 | // 20 total issues: fail 60 | [threshold: 20, type: 'TOTAL', unstable: false] 61 | ], 62 | tools: [ 63 | gcc(id: 'gcc-avr', name: 'gcc-avr'), 64 | ] 65 | ) 66 | } 67 | } 68 | } 69 | } 70 | post 71 | { 72 | always 73 | { 74 | // Scan for open tasks, warnings, issues, etc. 75 | recordIssues( 76 | healthy: 5, 77 | unhealthy: 10, 78 | aggregatingResults: true, 79 | referenceJobName: 'ea-nightly/arduino-printf/master', 80 | filters: [ 81 | excludeFile('subprojects/*') 82 | ], 83 | qualityGates: [ 84 | // 3 new issue: unstable 85 | [threshold: 3, type: 'DELTA', unstable: true], 86 | // 5 new issues: failed build 87 | [threshold: 5, type: 'DELTA', unstable: false], 88 | // 10 total issues: unstable 89 | [threshold: 10, type: 'TOTAL', unstable: true], 90 | // 20 total issues: fail 91 | [threshold: 20, type: 'TOTAL', unstable: false] 92 | ], 93 | tools: [ 94 | taskScanner( 95 | excludePattern: 'buildresults/**, subprojects/**, build/**, extras/**', 96 | includePattern: '**/*.c, **/*.cpp, **/*.h, **/*.hpp, **/*.sh, **/*.build, **/*.ino', 97 | normalTags: 'TODO, to do, WIP', 98 | highTags: 'FIXME, FIX', 99 | ignoreCase: true, 100 | ), 101 | ] 102 | ) 103 | 104 | gitTagCleanup "${params.MAJOR_VERSION}.${params.MINOR_VERSION}.${BUILD_NUMBER}" 105 | } 106 | success 107 | { 108 | gitTagSuccess "${params.MAJOR_VERSION}.${params.MINOR_VERSION}.${BUILD_NUMBER}" 109 | } 110 | failure 111 | { 112 | /* 113 | * This job does not have a GitHub configuration, 114 | * so we need to create a dummy config 115 | */ 116 | githubSetConfig('69e4682e-2951-492f-b828-da06364c322d') 117 | githubFileIssue() 118 | emailNotify(currentBuild.currentResult) 119 | } 120 | } 121 | } 122 | --------------------------------------------------------------------------------