├── .github └── workflows │ └── c-cpp.yml ├── .travis.yml ├── LICENSE ├── README.md ├── test ├── makefile └── test.c ├── xprintf.c └── xprintf.h /.github/workflows/c-cpp.yml: -------------------------------------------------------------------------------- 1 | name: C/C++ CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: make 17 | run: cd test && make test 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | sudo: false 3 | script: 4 | - cd test 5 | - make test 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Rafa García 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xprintf 2 | 3 | [![Build Status](https://travis-ci.org/rafagafe/xprintf.svg?branch=master)](https://travis-ci.org/rafagafe/xprintf) 4 | 5 | The xprintf module is an alternative implementation of the printf family of the standard library of C. 6 | 7 | The main feature of xprintf is that it lets you __print in user defined output stream__. In this way the user can print directly on a serial port or on an RTOS queue. 8 | 9 | * It supports almost all printf functionality except floating point. 10 | * It is a very light implementation. It is suitable for embedded systems. 11 | * It has a __low stack usage__ profile. It is suitable for multi-thread systems. 12 | * Supports several compilation options to reduce program memory by eliminating features. 13 | 14 | # Example 15 | 16 | We have a queue module with this interface: 17 | 18 | ```C 19 | 20 | // File: queue.h 21 | 22 | struct queue; 23 | 24 | void queue_push( struct queue* queue, unsigned char byte ); 25 | 26 | ``` 27 | 28 | We need print in this queue with a function like this: 29 | 30 | ```C 31 | 32 | // File: qprintf.h 33 | 34 | int qprintf( struct queue* queue, char const* fmt, ... ); 35 | 36 | ``` 37 | 38 | We implement the `qprintf` function by using the `xvprintf` function: 39 | 40 | ```C 41 | 42 | // File: qprintf.c 43 | 44 | #include "xprintf.h" 45 | 46 | /* Callback function for user defined output stream. */ 47 | static void print2queue( void* p, void const* src, int len ) { 48 | unsigned char* data = (unsigned char*)src; 49 | struct queue* queue = (struct queue*)p; 50 | for( int i = 0; i < len; ++i ) 51 | queue_push( queue, data[i] ); 52 | } 53 | 54 | /* Print formatted data to a queue. */ 55 | int qprintf( struct queue* queue, char const* fmt, ... ) { 56 | struct ostrm const ostrm = { 57 | .p = (void*)queue, 58 | .func = print2queue 59 | }; 60 | va_list va; 61 | va_start( va, fmt ); 62 | int const rslt = xvprintf( &ostrm, fmt, va ); 63 | va_end( va ); 64 | return rslt; 65 | } 66 | 67 | ``` 68 | -------------------------------------------------------------------------------- /test/makefile: -------------------------------------------------------------------------------- 1 | 2 | CC = gcc 3 | CFLAGS = -g -O2 -std=gnu99 -Wall -MMD 4 | 5 | build_dir = build 6 | 7 | test_src_dir = . 8 | test_obj_dir = $(build_dir)/test 9 | 10 | module_src_dir = .. 11 | module_obj_dir = $(build_dir)/module 12 | 13 | test_src = $(wildcard $(test_src_dir)/*.c) 14 | test_obj = $(patsubst $(test_src_dir)/%.c,$(test_obj_dir)/%.o,$(test_src)) 15 | 16 | module_src = $(wildcard $(module_src_dir)/*.c) 17 | module_obj = $(patsubst $(module_src_dir)/%.c,$(module_obj_dir)/%.o,$(module_src)) 18 | 19 | src = $(test_src) $(module_src) 20 | obj = $(test_obj) $(module_obj) 21 | dep = $(obj:.o=.d) 22 | 23 | .PHONY: build all clean 24 | 25 | build: $(build_dir)/module-test 26 | 27 | all: clean build 28 | 29 | clean: 30 | rm -rf $(dep) 31 | rm -rf $(obj) 32 | rm -rf $(build_dir)/module-test 33 | 34 | test: $(build_dir)/module-test 35 | $(build_dir)/module-test 36 | 37 | $(build_dir)/module-test: $(obj) 38 | $(CC) $(CFLAGS) -o $@ $^ 39 | 40 | $(test_obj_dir)/%.o: $(test_src_dir)/%.c 41 | mkdir -p $(@D) 42 | $(CC) $(CFLAGS) -c -o $@ $< 43 | 44 | $(module_obj_dir)/%.o: $(module_src_dir)/%.c 45 | mkdir -p $(@D) 46 | $(CC) $(CFLAGS) -c -o $@ $< 47 | 48 | -include $(dep) 49 | -------------------------------------------------------------------------------- /test/test.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 4 | 5 | Licensed under the MIT License . 6 | SPDX-License-Identifier: MIT 7 | Copyright (c) 2016-2018 Rafa Garcia . 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include "../xprintf.h" 36 | 37 | static int check( int len1, char const* str1, int len2, char const* str2, int size ) { 38 | enum { margin = 15 }; 39 | if ( len1 != len2 ) { 40 | puts( "FAILED" ); 41 | printf( "%*s'%d'\n", margin, "-Len 1:", len1 ); 42 | printf( "%*s'%d'\n\n", margin, "-Len 2:", len2 ); 43 | printf( "%*s'%.*s'\n", margin, "-String 1:", len1, str1 ); 44 | printf( "%*s'%.*s'\n\n", margin, "-String 2:", len2, str2 ); 45 | return -1; 46 | } 47 | if ( 0 != strcmp( str1, str2 ) ) { 48 | puts( "FAILED" ); 49 | printf( "%*s'%s'\n", margin, "-String 1:", str1 ); 50 | printf( "%*s'%s'\n\n", margin, "-String 2:", str2 ); 51 | return -2; 52 | } 53 | if ( 0 != memcmp( str1, str2, size ) ) { 54 | puts( "FAILED" ); 55 | printf( "%*s'%.*s'\n", margin, "-Padding 1:", size - len1 - 1, str1 + len1 + 1 ); 56 | printf( "%*s'%.*s'\n\n", margin, "-Padding 2:", size - len2 - 1, str2 + len2 + 1 ); 57 | return -3; 58 | } 59 | puts( "PASSED" ); 60 | return 0; 61 | } 62 | 63 | int main( void ) { 64 | 65 | enum { 66 | verbose = 0, 67 | wval = 16, 68 | wfmt = 10, 69 | wrslt = 8 70 | }; 71 | 72 | int total = 0; 73 | int passed = 0; 74 | 75 | printf( "\n%-*s%-*s%s\n", wfmt, "Format", wval, "Value", "Result" ); 76 | for( int i = 0; i < wfmt + wval + wrslt; ++i ) 77 | putc( '-', stdout ); 78 | putc('\n', stdout ); 79 | 80 | char str1[ 128 ]; 81 | char str2[ sizeof str1 ]; 82 | 83 | { 84 | static char const* const data [] = { "abcdefg" }; 85 | static char const* const fmt [] = { 86 | "Text: %s", "%10s", "%-10s", "%.2s", "%.10s", "%10.2s", "%10.2s" 87 | }; 88 | for( int i = 0; i < sizeof fmt / sizeof *fmt; ++i ) { 89 | for( int j = 0; j < sizeof data / sizeof *data; ++j ) { 90 | memset( str1, 'R', sizeof str1 ); 91 | memset( str2, 'R', sizeof str2 ); 92 | printf( "%-*s%-*s", wfmt, fmt[i], wval, data[j] ); 93 | int len1 = sprintf( str1, fmt[i], data[j] ); 94 | int len2 = xsprintf( str2, fmt[i], data[j] ); 95 | int err = check( len1, str1, len2, str2, sizeof str1 ); 96 | passed += 0 == err; 97 | ++total; 98 | } 99 | } 100 | } 101 | 102 | { 103 | memset( str1, 'R', sizeof str1 ); 104 | memset( str2, 'R', sizeof str2 ); 105 | static char const fmt [] = "Hi %s%n."; 106 | static char const data [] = "Perry"; 107 | printf( "%-*s%-*s", wfmt, fmt, wval, data ); 108 | int n1 = -1; 109 | int len1 = sprintf( str1, fmt, data, &n1 ); 110 | int n2 = -1; 111 | int len2 = xsprintf( str2, fmt, data, &n2 ); 112 | int err = 0; 113 | if ( n1 != n2 || 0 > n1 ) { 114 | puts( "FAILED" ); 115 | err = 1; 116 | } 117 | else 118 | err = check( len1, str1, len2, str2, sizeof str1 ); 119 | passed += 0 == err; 120 | ++total; 121 | } 122 | 123 | { 124 | static int const data [] = { -12345, 12345, 0, 98, -98 }; 125 | static char const* const fmt [] = { 126 | "%d", "%7d", "%07d", "%-7d", "%+d", "%.4d", "%.4d", "%+5.4d" 127 | }; 128 | for( int i = 0; i < sizeof fmt / sizeof *fmt; ++i ) { 129 | for( int j = 0; j < sizeof data / sizeof *data; ++j ) { 130 | memset( str1, 'R', sizeof str1 ); 131 | memset( str2, 'R', sizeof str2 ); 132 | printf( "%-*s%-*d", wfmt, fmt[i], wval, data[j] ); 133 | int len1 = sprintf( str1, fmt[i], data[j] ); 134 | int len2 = xsprintf( str2, fmt[i], data[j] ); 135 | int err = check( len1, str1, len2, str2, sizeof str1 ); 136 | passed += 0 == err; 137 | ++total; 138 | } 139 | } 140 | } 141 | 142 | { 143 | static size_t const data [] = { 12345, 0, 98 }; 144 | static char const* const fmt [] = { 145 | "%zd", "%7zd", "%07zd", "%-7zd", "%+zd", "%.4zd", "%.4zd", "%+5.4zd" 146 | }; 147 | for( int i = 0; i < sizeof fmt / sizeof *fmt; ++i ) { 148 | for( int j = 0; j < sizeof data / sizeof *data; ++j ) { 149 | memset( str1, 'R', sizeof str1 ); 150 | memset( str2, 'R', sizeof str2 ); 151 | printf( "%-*s%-*ld", wfmt, fmt[i], wval, data[j] ); 152 | int len1 = sprintf( str1, fmt[i], data[j] ); 153 | int len2 = xsprintf( str2, fmt[i], data[j] ); 154 | int err = check( len1, str1, len2, str2, sizeof str1 ); 155 | passed += 0 == err; 156 | ++total; 157 | } 158 | } 159 | } 160 | 161 | { 162 | static long int const data [] = { -12345, 12345, 0, 98, -98 }; 163 | static char const* const fmt [] = { 164 | "%ld", "%7ld", "%07ld", "%-7ld", "%+ld", "%.4ld", "%.4ld", "%+5.4ld" 165 | }; 166 | for( int i = 0; i < sizeof fmt / sizeof *fmt; ++i ) { 167 | for( int j = 0; j < sizeof data / sizeof *data; ++j ) { 168 | memset( str1, 'R', sizeof str1 ); 169 | memset( str2, 'R', sizeof str2 ); 170 | printf( "%-*s%-*ld", wfmt, fmt[i], wval, data[j] ); 171 | int len1 = sprintf( str1, fmt[i], data[j] ); 172 | int len2 = xsprintf( str2, fmt[i], data[j] ); 173 | int err = check( len1, str1, len2, str2, sizeof str1 ); 174 | passed += 0 == err; 175 | ++total; 176 | } 177 | } 178 | } 179 | 180 | { 181 | static long long int const data [] = { -12345, 12345, 0, 98, -98 }; 182 | static char const* const fmt [] = { 183 | "%lld", "%7lld", "%07lld", "%-7lld", "%+lld", "%.4lld", "%.4lld", "%+5.4lld" 184 | }; 185 | for( int i = 0; i < sizeof fmt / sizeof *fmt; ++i ) { 186 | for( int j = 0; j < sizeof data / sizeof *data; ++j ) { 187 | memset( str1, 'R', sizeof str1 ); 188 | memset( str2, 'R', sizeof str2 ); 189 | printf( "%-*s%-*lld", wfmt, fmt[i], wval, data[j] ); 190 | int len1 = sprintf( str1, fmt[i], data[j] ); 191 | int len2 = xsprintf( str2, fmt[i], data[j] ); 192 | int err = check( len1, str1, len2, str2, sizeof str1 ); 193 | passed += 0 == err; 194 | ++total; 195 | } 196 | } 197 | } 198 | 199 | { 200 | static unsigned int const data [] = { 12345, 0, 98 }; 201 | static char const* const fmt [] = { 202 | "%u", "%7u", "%07u", "%-7u", "%+u", "%.4u", "%.4u", "%+5.4u" 203 | }; 204 | for( int i = 0; i < sizeof fmt / sizeof *fmt; ++i ) { 205 | for( int j = 0; j < sizeof data / sizeof *data; ++j ) { 206 | memset( str1, 'R', sizeof str1 ); 207 | memset( str2, 'R', sizeof str2 ); 208 | printf( "%-*s%-*u", wfmt, fmt[i], wval, data[j] ); 209 | int len1 = sprintf( str1, fmt[i], data[j] ); 210 | int len2 = xsprintf( str2, fmt[i], data[j] ); 211 | int err = check( len1, str1, len2, str2, sizeof str1 ); 212 | passed += 0 == err; 213 | ++total; 214 | } 215 | } 216 | } 217 | 218 | { 219 | static size_t const data [] = { 12345, 0, 98 }; 220 | static char const* const fmt [] = { 221 | "%zu", "%7zu", "%07zu", "%-7zu", "%+zu", "%.4zu", "%.4zu", "%+5.4zu" 222 | }; 223 | for( int i = 0; i < sizeof fmt / sizeof *fmt; ++i ) { 224 | for( int j = 0; j < sizeof data / sizeof *data; ++j ) { 225 | memset( str1, 'R', sizeof str1 ); 226 | memset( str2, 'R', sizeof str2 ); 227 | printf( "%-*s%-*lu", wfmt, fmt[i], wval, data[j] ); 228 | int len1 = sprintf( str1, fmt[i], data[j] ); 229 | int len2 = xsprintf( str2, fmt[i], data[j] ); 230 | int err = check( len1, str1, len2, str2, sizeof str1 ); 231 | passed += 0 == err; 232 | ++total; 233 | } 234 | } 235 | } 236 | 237 | { 238 | static unsigned long int const data [] = { 12345, 0, 98 }; 239 | static char const* const fmt [] = { 240 | "%lu", "%7lu", "%07lu", "%-7lu", "%+lu", "%.4lu", "%.4lu", "%+5.4lu" 241 | }; 242 | for( int i = 0; i < sizeof fmt / sizeof *fmt; ++i ) { 243 | for( int j = 0; j < sizeof data / sizeof *data; ++j ) { 244 | memset( str1, 'R', sizeof str1 ); 245 | memset( str2, 'R', sizeof str2 ); 246 | printf( "%-*s%-*lu", wfmt, fmt[i], wval, data[j] ); 247 | int len1 = sprintf( str1, fmt[i], data[j] ); 248 | int len2 = xsprintf( str2, fmt[i], data[j] ); 249 | int err = check( len1, str1, len2, str2, sizeof str1 ); 250 | passed += 0 == err; 251 | ++total; 252 | } 253 | } 254 | } 255 | 256 | { 257 | static unsigned long long int const data [] = { 12345, 0, 98 }; 258 | static char const* const fmt [] = { 259 | "%llu", "%7llu", "%07llu", "%-7llu", "%+llu", "%.4llu", "%.4llu", "%+5.4llu" 260 | }; 261 | for( int i = 0; i < sizeof fmt / sizeof *fmt; ++i ) { 262 | for( int j = 0; j < sizeof data / sizeof *data; ++j ) { 263 | memset( str1, 'R', sizeof str1 ); 264 | memset( str2, 'R', sizeof str2 ); 265 | printf( "%-*s%-*llu", wfmt, fmt[i], wval, data[j] ); 266 | int len1 = sprintf( str1, fmt[i], data[j] ); 267 | int len2 = xsprintf( str2, fmt[i], data[j] ); 268 | int err = check( len1, str1, len2, str2, sizeof str1 ); 269 | passed += 0 == err; 270 | ++total; 271 | } 272 | } 273 | } 274 | 275 | { 276 | static unsigned int const data [] = { 12345, 0, 98, 1 }; 277 | static char const* const fmt [] = { 278 | "%X", "%x", "%#x", "%7x", "%#7x", "%07x", "%-7x", "%+x", "%.4x", "%.4x", "%+5.4x" 279 | }; 280 | for( int i = 0; i < sizeof fmt / sizeof *fmt; ++i ) { 281 | for( int j = 0; j < sizeof data / sizeof *data; ++j ) { 282 | memset( str1, 'R', sizeof str1 ); 283 | memset( str2, 'R', sizeof str2 ); 284 | printf( "%-*s%-*u", wfmt, fmt[i], wval, data[j] ); 285 | int len1 = sprintf( str1, fmt[i], data[j] ); 286 | int len2 = xsprintf( str2, fmt[i], data[j] ); 287 | int err = check( len1, str1, len2, str2, sizeof str1 ); 288 | passed += 0 == err; 289 | ++total; 290 | } 291 | } 292 | } 293 | 294 | { 295 | static size_t const data [] = { 12345, 0, 98, 1 }; 296 | static char const* const fmt [] = { 297 | "%zX", "%zx", "%#zx", "%7zx", "%07zx", "%-7zx", "%+zx", "%.4zx", "%.4zx", "%+5.4zx" 298 | }; 299 | for( int i = 0; i < sizeof fmt / sizeof *fmt; ++i ) { 300 | for( int j = 0; j < sizeof data / sizeof *data; ++j ) { 301 | memset( str1, 'R', sizeof str1 ); 302 | memset( str2, 'R', sizeof str2 ); 303 | printf( "%-*s%-*lu", wfmt, fmt[i], wval, data[j] ); 304 | int len1 = sprintf( str1, fmt[i], data[j] ); 305 | int len2 = xsprintf( str2, fmt[i], data[j] ); 306 | int err = check( len1, str1, len2, str2, sizeof str1 ); 307 | passed += 0 == err; 308 | ++total; 309 | } 310 | } 311 | } 312 | 313 | { 314 | static unsigned long int const data [] = { 12345, 0, 98, 1 }; 315 | static char const* const fmt [] = { 316 | "%lX", "%lx", "%#lx", "%7lx", "%07lx", "%-7lx", "%+lx", "%.4lx", "%.4lx", "%+5.4lx" 317 | }; 318 | for( int i = 0; i < sizeof fmt / sizeof *fmt; ++i ) { 319 | for( int j = 0; j < sizeof data / sizeof *data; ++j ) { 320 | memset( str1, 'R', sizeof str1 ); 321 | memset( str2, 'R', sizeof str2 ); 322 | printf( "%-*s%-*lu", wfmt, fmt[i], wval, data[j] ); 323 | int len1 = sprintf( str1, fmt[i], data[j] ); 324 | int len2 = xsprintf( str2, fmt[i], data[j] ); 325 | int err = check( len1, str1, len2, str2, sizeof str1 ); 326 | passed += 0 == err; 327 | ++total; 328 | } 329 | } 330 | } 331 | 332 | { 333 | static unsigned long long int const data [] = { 12345, 0, 98, 1 }; 334 | static char const* const fmt [] = { 335 | "%llX", "%llx", "%#llx", "%7llx", "%07llx", "%-7llx", "%+llx", "%.4llx", "%.4llx", "%+5.4llx" 336 | }; 337 | for( int i = 0; i < sizeof fmt / sizeof *fmt; ++i ) { 338 | for( int j = 0; j < sizeof data / sizeof *data; ++j ) { 339 | memset( str1, 'R', sizeof str1 ); 340 | memset( str2, 'R', sizeof str2 ); 341 | printf( "%-*s%-*llu", wfmt, fmt[i], wval, data[j] ); 342 | int len1 = sprintf( str1, fmt[i], data[j] ); 343 | int len2 = xsprintf( str2, fmt[i], data[j] ); 344 | int err = check( len1, str1, len2, str2, sizeof str1 ); 345 | passed += 0 == err; 346 | ++total; 347 | } 348 | } 349 | } 350 | 351 | { 352 | static unsigned char const data [] = { 'a' }; 353 | static char const* const fmt [] = { 354 | "%c", "%10c", "%-10c", "%.2c", "%.10c", "%10.2c" 355 | }; 356 | for( int i = 0; i < sizeof fmt / sizeof *fmt; ++i ) { 357 | for( int j = 0; j < sizeof data / sizeof *data; ++j ) { 358 | memset( str1, 'R', sizeof str1 ); 359 | memset( str2, 'R', sizeof str2 ); 360 | printf( "%-*s%-*c", wfmt, fmt[i], wval, data[j] ); 361 | int len1 = sprintf( str1, fmt[i], data[j] ); 362 | int len2 = xsprintf( str2, fmt[i], data[j] ); 363 | int err = check( len1, str1, len2, str2, sizeof str1 ); 364 | passed += 0 == err; 365 | ++total; 366 | } 367 | } 368 | } 369 | 370 | { 371 | static char const array [] = "array"; 372 | static int const arraylen = sizeof array - 1; 373 | static char const* const fmt[] = { "%.*s", "%*s", "%-*s" }; 374 | for( int i = 0; i < sizeof fmt / sizeof *fmt; ++i ) { 375 | memset( str1, 'R', sizeof str1 ); 376 | memset( str2, 'R', sizeof str2 ); 377 | printf( "%-*s%-*.*s", wfmt, fmt[i], wval, arraylen, array ); 378 | int len1 = sprintf( str1, fmt[i], arraylen, array ); 379 | int len2 = xsprintf( str2, fmt[i], arraylen, array ); 380 | int err = check( len1, str1, len2, str2, sizeof str1 ); 381 | passed += 0 == err; 382 | ++total; 383 | } 384 | } 385 | 386 | { 387 | static void* const data [] = { (void*)12345, (void*)0, (void*)98, (void*)1 }; 388 | static char const* const fmt [] = { 389 | "%p", "%#p", "%7p", "%07p", "%-7p", "%.4p", "%.4p" 390 | }; 391 | for( int i = 0; i < sizeof fmt / sizeof *fmt; ++i ) { 392 | for( int j = 0; j < sizeof data / sizeof *data; ++j ) { 393 | memset( str1, 'R', sizeof str1 ); 394 | memset( str2, 'R', sizeof str2 ); 395 | printf( "%-*s%-*p", wfmt, fmt[i], wval, data[j] ); 396 | int len1 = sprintf( str1, fmt[i], data[j] ); 397 | int len2 = xsprintf( str2, fmt[i], data[j] ); 398 | int err = check( len1, str1, len2, str2, sizeof str1 ); 399 | passed += 0 == err; 400 | ++total; 401 | } 402 | } 403 | } 404 | 405 | printf( "\nTest passed: [ %d / %d ]\n", passed, total ); 406 | int failed = total - passed; 407 | printf( "\n%s%s\n\n", "Test suit result: ", failed ? "FAILED" : "PASSED" ); 408 | return failed; 409 | } 410 | -------------------------------------------------------------------------------- /xprintf.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 4 | 5 | Licensed under the MIT License . 6 | SPDX-License-Identifier: MIT 7 | Copyright (c) 2016-2018 Rafa Garcia . 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | */ 25 | 26 | #include "xprintf.h" 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #ifdef USE_XPRNTF_CFG 35 | #include "xprintf-cfg.h" 36 | #else 37 | enum { 38 | diswidth = 0, 39 | disprec = 0, 40 | disu = 0, 41 | disd = 0, 42 | disx = 0, 43 | disz = 0, 44 | disl = 0, 45 | disll = 0, 46 | disn = 0 47 | }; 48 | #endif 49 | 50 | /** Get flag from format string. 51 | * @param flag Destination '-', '+', ' ', '#', '0' or 'n' for none 52 | * @param fmt Format string. 53 | * @return num bytes read from format string. 0 ir 1. */ 54 | static int getflag( int* flag, char const* fmt ) { 55 | static char const flags[] = { '-', '+', ' ', '#', '0' }; 56 | for( int i = 0; i < sizeof flags; ++i ) { 57 | if ( flags[i] == *fmt ) { 58 | *flag = flags[i]; 59 | return 1; 60 | } 61 | } 62 | *flag = 'n'; 63 | return 0; 64 | } 65 | 66 | /** Get and number from format string. 67 | * @param dest Destination. 68 | * @param fmt Format string. 69 | * @return num bytes read from format string. */ 70 | static int getnum( int* dest, char const* fmt ) { 71 | int const max = 6; 72 | *dest = 0; 73 | for( int len = 0; max > len; ++len ) { 74 | if ( !isdigit( (unsigned char)fmt[len] ) ) 75 | return len; 76 | *dest *= 10; 77 | *dest += fmt[ len ] - '0'; 78 | } 79 | return max; 80 | } 81 | 82 | /** Get and number from format string or from va_list i an * is found. 83 | * @param dest Destination. 84 | * @param va 85 | * @param fmt Format string. 86 | * @return num bytes read from format string. */ 87 | #define getval( dest, va, src ) ({ \ 88 | int rslt = 0; \ 89 | if ( isdigit( (unsigned char)*src ) ) \ 90 | rslt = getnum( dest, src ); \ 91 | else if ( '*' == *src ) { \ 92 | *dest = va_arg( va, int ); \ 93 | rslt = 1; \ 94 | } \ 95 | else \ 96 | rslt = 0; \ 97 | rslt; \ 98 | }) 99 | 100 | /** Reverse a string. 101 | * @param str String to be reversed. 102 | * @param len Length of the string. */ 103 | static void reverse( char str[], int len ) { 104 | int start = 0; 105 | int end = len -1; 106 | while( start < end ) { 107 | char const tmp = str[ start ]; 108 | str[ start ] = str[ end ]; 109 | str[ end ] = tmp; 110 | ++start; 111 | --end; 112 | } 113 | } 114 | 115 | /** Print an integer in hexadecimal base in a buffer. 116 | * @param dest Destination buffer. 117 | * @param val Value to be printed. 118 | * @param upper Print hexa digit in upper or lower case. 119 | * @return Numbytes printed. */ 120 | #define x2a( dest, val, upper ) ({ \ 121 | char const* const ptr = upper ? \ 122 | "0123456789ABCDEF": \ 123 | "0123456789abcdef"; \ 124 | int len = 0; \ 125 | while ( 0 != val ) { \ 126 | int const nibble = val % 16; \ 127 | dest[ len++ ] = ptr[ nibble ]; \ 128 | val /= 16; \ 129 | } \ 130 | if ( 0 == len ) { \ 131 | *dest = '0'; \ 132 | len = 1; \ 133 | } \ 134 | dest[ len ] = '\0'; \ 135 | reverse( dest, len ); \ 136 | len; \ 137 | }) 138 | 139 | /** Print an unsigned integer in decimal base in a buffer. 140 | * @param dest Destination buffer. 141 | * @param val Value to be printed. 142 | * @return Numbytes printed. */ 143 | #define u2a( dest, val ) ({ \ 144 | int len = 0; \ 145 | while ( 0 != val ) { \ 146 | unsigned int const rem = val % 10; \ 147 | dest[ len++ ] = rem + '0'; \ 148 | val /= 10; \ 149 | } \ 150 | if ( 0 == len ) { \ 151 | *dest = '0'; \ 152 | len = 1; \ 153 | } \ 154 | dest[ len ] = '\0'; \ 155 | reverse( dest, len ); \ 156 | len; \ 157 | }) 158 | 159 | /** Print a signed integer in decimal base in a buffer. 160 | * @param dest Destination buffer. 161 | * @param val Value to be printed. 162 | * @param sign Force print '+' 163 | * @return Numbytes printed. */ 164 | #define i2a( dest, val, sign ) ({ \ 165 | int const neg = 0 > val; \ 166 | if ( neg ) { \ 167 | *dest = '-'; \ 168 | val = -val; \ 169 | } \ 170 | else if ( sign ) \ 171 | *dest = '+'; \ 172 | char* ptr = dest + ( neg || sign ); \ 173 | ( neg || sign ) + u2a( ptr, val ); \ 174 | }) 175 | 176 | /** Send to an output stream a memory block. 177 | * @param obj Destination output stream. 178 | * @param src Source memory block. 179 | * @param len Length in bytes of the memory block. */ 180 | static void ostrm( struct ostrm const* obj, void const* src, int len ) { 181 | obj->func( obj->p, src, len ); 182 | } 183 | 184 | /** Send to an output stream a character. 185 | * @param obj Destination output stream. 186 | * @param ch character value. */ 187 | static void ostrmch( struct ostrm const* obj, unsigned char ch ) { 188 | ostrm( obj, &ch, sizeof ch ); 189 | } 190 | 191 | /** Send to an output stream a character several times. 192 | * @param obj Destination output stream. 193 | * @param ch Character value. 194 | * @param qty Number of times. */ 195 | static void ostrmchq( struct ostrm const* obj, unsigned char ch, int qty ) { 196 | for( int i = 0; i < qty; ++i ) 197 | ostrm( obj, &ch, sizeof ch ); 198 | } 199 | 200 | #ifndef max 201 | #define max( a, b ) ( (a) > (b) ? (a) : (b) ) 202 | #endif 203 | 204 | /** Send to an output stream a string with a number. 205 | * @param obj Destination output stream. 206 | * @param buff source string with a number. 207 | * @param len Length in bytes of the string. 208 | * @param width Width got in the format string. 209 | * @param flag Flag got in the format string. 210 | * @param prec Precision got in the format string. */ 211 | static int sendnum( struct ostrm const* obj, char const* buff, int len, int width, int flag, int prec, char base ) { 212 | 213 | int rslt = 0; 214 | 215 | int const hassign = '-' == *buff || '+' == *buff; 216 | int const hasbase = '\0' != base && '0' != *buff; 217 | int const numdigits = len - hassign; 218 | int const hasprec = INT_MAX != prec; 219 | int const zerosprec = hasprec * ( max( numdigits, prec ) - numdigits ); 220 | int const totallen = numdigits + zerosprec + hassign + 2*hasbase; 221 | int const padding = width > totallen ? width - totallen : 0; 222 | int const leftjust = '-' == flag; 223 | int const zerospadding = ( !hasprec && '0' == flag && !leftjust ) * padding; 224 | int const leftpadding = ( !zerospadding && !leftjust ) * padding; 225 | int const rigthpadding = leftjust * padding; 226 | int const numzeros = max( zerospadding, zerosprec ); 227 | 228 | ostrmchq( obj, ' ', leftpadding ); 229 | rslt += leftpadding; 230 | 231 | if ( hasbase ) { 232 | ostrmch( obj, '0' ); 233 | ostrmch( obj, base ); 234 | rslt += 2; 235 | } 236 | 237 | if ( hassign && 0 != numzeros ) { 238 | ostrmch( obj, *buff ); 239 | ++buff, --len, ++rslt; 240 | } 241 | 242 | ostrmchq( obj, '0', numzeros ); 243 | rslt += numzeros; 244 | 245 | ostrm( obj, buff, len ); 246 | rslt += len; 247 | 248 | ostrmchq( obj, ' ', rigthpadding ); 249 | rslt += rigthpadding; 250 | 251 | return rslt; 252 | } 253 | 254 | /* Header compare */ 255 | static int hdrcmp( char const* header, char const* str ) { 256 | int len; 257 | for( len = 0; '\0' != *header; ++len, ++str, ++header ) 258 | if ( *header != *str ) 259 | return 0; 260 | return len; 261 | } 262 | 263 | enum argtype { none, l, ll, z }; 264 | 265 | enum argtype gettype( char const** fmt ) { 266 | static struct { char const* token; enum argtype type; } const lut [] = { 267 | { "ll", ll }, 268 | { "l", l }, 269 | { "z", z } 270 | }; 271 | for( int i = 0; i < sizeof lut / sizeof *lut; ++i ) { 272 | int len = hdrcmp( lut[i].token, *fmt ); 273 | if ( len ) { 274 | *fmt += len; 275 | return lut[i].type; 276 | } 277 | } 278 | return none; 279 | } 280 | 281 | 282 | /* Print formatted data to a user defined stream. */ 283 | int xvprintf( struct ostrm const* o, char const* fmt, va_list va ) { 284 | 285 | char buff[ 22 ]; 286 | 287 | int rslt = 0; 288 | 289 | while( '\0' != *fmt ) { 290 | 291 | if( '%' != *fmt ) { 292 | int len = 0; 293 | while( '\0' != fmt[len] && '%' != fmt[len] ) 294 | ++len; 295 | ostrm( o, fmt, len ); 296 | rslt += len; 297 | fmt += len; 298 | continue; 299 | } 300 | ++fmt; 301 | 302 | int flag; 303 | fmt += getflag( &flag, fmt ); 304 | 305 | int width = 0; 306 | fmt += getval( &width, va, fmt ); 307 | if ( 0 > width ) { 308 | flag = '-'; 309 | width = -width; 310 | } 311 | if ( diswidth ) 312 | width = 0; 313 | 314 | int precision = INT_MAX; 315 | if ( '.' == *fmt ) { 316 | ++fmt; 317 | fmt += getval( &precision, va, fmt ); 318 | } 319 | if ( disprec ) 320 | precision = INT_MAX; 321 | 322 | enum argtype const type = gettype( &fmt ); 323 | int const specifier = *fmt++; 324 | switch( specifier ) { 325 | case '\0': 326 | return rslt; 327 | case '%': 328 | ostrmch( o, '%' ); 329 | ++rslt; 330 | break; 331 | case 'u': { 332 | if ( disu ) 333 | return rslt; 334 | int len; 335 | switch( type ) { 336 | case none: { 337 | unsigned int val = va_arg( va, unsigned int ); 338 | len = u2a( buff, val ); 339 | break; 340 | } 341 | case z: { 342 | if ( disz ) 343 | return rslt; 344 | size_t val = va_arg( va, size_t ); 345 | len = u2a( buff, val ); 346 | break; 347 | } 348 | case l: { 349 | if ( disl ) 350 | return rslt; 351 | unsigned long int val = va_arg( va, unsigned long int ); 352 | len = u2a( buff, val ); 353 | break; 354 | } 355 | case ll: { 356 | if ( disll ) 357 | return rslt; 358 | unsigned long long int val = va_arg( va, unsigned long long int ); 359 | len = u2a( buff, val ); 360 | break; 361 | } 362 | default: 363 | return rslt; 364 | } 365 | rslt += sendnum( o, buff, len, width, flag, precision, '\0' ); 366 | break; 367 | } 368 | case 'i': 369 | case 'd': { 370 | if ( disd ) 371 | return rslt; 372 | int len; 373 | switch( type ) { 374 | case none: { 375 | int val = va_arg( va, int ); 376 | len = i2a( buff, val, '+' == flag ); 377 | break; 378 | } 379 | case z: { 380 | if ( disz ) 381 | return rslt; 382 | size_t val = va_arg( va, size_t ); 383 | len = i2a( buff, val, '+' == flag ); 384 | break; 385 | } 386 | case l: { 387 | if ( disl ) 388 | return rslt; 389 | long int val = va_arg( va, long int ); 390 | len = i2a( buff, val, '+' == flag ); 391 | break; 392 | } 393 | case ll: { 394 | if ( disll ) 395 | return rslt; 396 | long long int val = va_arg( va, long long int ); 397 | len = i2a( buff, val, '+' == flag ); 398 | break; 399 | } 400 | default: 401 | return rslt; 402 | } 403 | rslt += sendnum( o, buff, len, width, flag, precision, '\0' ); 404 | break; 405 | } 406 | case 'x': 407 | case 'X': { 408 | if ( disx ) 409 | return rslt; 410 | int const upper = 'X' == specifier; 411 | int len; 412 | switch( type ) { 413 | case none: { 414 | unsigned int val = va_arg( va, unsigned int ); 415 | len = x2a( buff, val, upper ); 416 | break; 417 | } 418 | case z: { 419 | if ( disz ) 420 | return rslt; 421 | size_t val = va_arg( va, size_t ); 422 | len = x2a( buff, val, upper ); 423 | break; 424 | } 425 | case l: { 426 | if ( disl ) 427 | return rslt; 428 | unsigned long int val = va_arg( va, unsigned long int ); 429 | len = x2a( buff, val, upper ); 430 | break; 431 | } 432 | case ll: { 433 | if ( disll ) 434 | return rslt; 435 | unsigned long long int val = va_arg( va, unsigned long long int ); 436 | len = x2a( buff, val, upper ); 437 | break; 438 | } 439 | default: 440 | return rslt; 441 | } 442 | int const base = '#' == flag ? specifier : '\0'; 443 | rslt += sendnum( o, buff, len, width, flag, precision, base ); 444 | break; 445 | } 446 | case 'p': { 447 | if ( disx ) 448 | return rslt; 449 | uintptr_t val = (uintptr_t)va_arg( va, void* ); 450 | char const* ptr; 451 | int len; 452 | int base; 453 | if ( 0 != val ) { 454 | ptr = buff; 455 | len = x2a( buff, val, 0 ); 456 | base = 'x'; 457 | } 458 | else { 459 | static char const nullval[] = "(nil)"; 460 | ptr = nullval; 461 | len = sizeof nullval - 1; 462 | base = '\0'; 463 | if ( '0' == flag ) 464 | flag = 'n'; 465 | } 466 | rslt += sendnum( o, ptr, len, width, flag, precision, base ); 467 | break; 468 | } 469 | case 'n': { 470 | if ( disn ) 471 | return rslt; 472 | int* val = va_arg( va, int* ); 473 | if ( NULL != val ) 474 | *val = rslt; 475 | break; 476 | } 477 | case 'c': 478 | case 's': { 479 | char tmp; 480 | char const* val; 481 | int vallen; 482 | if ( 's' == specifier ) { 483 | val = va_arg( va, char* ); 484 | if ( NULL == val ) 485 | val = "(null)"; 486 | vallen = strlen( val ); 487 | } 488 | else { 489 | tmp = va_arg( va, int ); 490 | val = &tmp; 491 | vallen = 1; 492 | } 493 | int const len = precision < vallen ? precision : vallen; 494 | if ( '-' != flag && width > len ) { 495 | int const diff = width - len; 496 | for( int i = 0; i < diff; ++i ) 497 | ostrmch( o, ' ' ); 498 | rslt += diff; 499 | } 500 | ostrm( o, val, len ); 501 | rslt += len; 502 | if ( '-' == flag && width > len ) { 503 | int const diff = width - len; 504 | for( int i = 0; i < diff; ++i ) 505 | ostrmch( o, ' ' ); 506 | rslt += diff; 507 | } 508 | break; 509 | } 510 | default: 511 | return rslt; 512 | } 513 | } 514 | return rslt; 515 | } 516 | 517 | /* Print formatted data to a user defined stream. */ 518 | int xprintf( struct ostrm const* o, char const* fmt, ... ) { 519 | va_list va; 520 | va_start( va, fmt ); 521 | int const rslt = xvprintf( o, fmt, va ); 522 | va_end( va ); 523 | return rslt; 524 | } 525 | 526 | static void putfile( void* p, void const* src, int len ) { 527 | char const* ptr = (char*)src; 528 | for( int i = 0; i < len; ++i ) 529 | fputc( ptr[i], p ); 530 | } 531 | 532 | /* Print formatted data to a file. */ 533 | int xfprintf( FILE* fd, char const* fmt, ... ) { 534 | struct ostrm const ostrm = { .p = fd, .func = putfile }; 535 | va_list va; 536 | va_start( va, fmt ); 537 | int const rslt = xvprintf( &ostrm, fmt, va ); 538 | va_end( va ); 539 | return rslt; 540 | } 541 | 542 | static void putbuff( void* p, void const* src, int len ) { 543 | char** buff = p; 544 | memcpy( *buff, src, len ); 545 | *buff += len; 546 | } 547 | 548 | /* Print formatted data to a buffer. */ 549 | int xsprintf( char* buff, char const* fmt, ... ) { 550 | char* tmp = buff; 551 | struct ostrm const ostrm = { .p = &tmp, .func = putbuff }; 552 | va_list va; 553 | va_start( va, fmt ); 554 | int const rslt = xvprintf( &ostrm, fmt, va ); 555 | va_end( va ); 556 | buff[ rslt ] = '\0'; 557 | return rslt; 558 | } 559 | -------------------------------------------------------------------------------- /xprintf.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 4 | 5 | Licensed under the MIT License . 6 | SPDX-License-Identifier: MIT 7 | Copyright (c) 2016-2018 Rafa Garcia . 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | */ 25 | 26 | #ifndef XPRINTF_H 27 | #define XPRINTF_H 28 | 29 | #ifdef __cplusplus 30 | extern "C" { 31 | #endif 32 | 33 | #include 34 | #include 35 | 36 | /** @defgroup xprintf 37 | * @{ */ 38 | 39 | /** Holds a user defined output stream */ 40 | struct ostrm { 41 | /** Parameter to pass the the function. */ 42 | void* p; 43 | /** Send a memory block ta a user defined stream. 44 | * @param p Parameter. 45 | * @param src Source memory block. 46 | * @param len Length in bytes of the memory block. */ 47 | void(*func)(void*p,void const*src,int len); 48 | }; 49 | 50 | /** Print formatted data to a user defined stream. */ 51 | int xprintf( struct ostrm const* o, char const* fmt, ... ) __attribute__ ((format (printf, 2, 3))); 52 | 53 | /** Print formatted data to a user defined stream. */ 54 | int xvprintf( struct ostrm const* o, char const* fmt, va_list va ); 55 | 56 | /** Print formatted data to a buffer. */ 57 | int xsprintf( char* buff, char const* fmt, ... ) __attribute__ ((format (printf, 2, 3))); 58 | 59 | /** Print formatted data to a file. */ 60 | int xfprintf( FILE* fd, char const* fmt, ... ) __attribute__ ((format (printf, 2, 3))); 61 | 62 | /** @ } */ 63 | 64 | #ifdef __cplusplus 65 | } 66 | #endif 67 | 68 | #endif /* XPRINTF_H */ --------------------------------------------------------------------------------