├── Formatters.h ├── LICENSE ├── Printf.h ├── README.md └── Test.cpp /Formatters.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef FORMATTERS_20160922_H_ 3 | #define FORMATTERS_20160922_H_ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace cxx17 { 11 | 12 | // This context writes to a buffer 13 | struct buffer_writer { 14 | 15 | template 16 | buffer_writer(char (&buffer)[N]) 17 | : ptr_(buffer), size_(N) { 18 | } 19 | 20 | buffer_writer(char *buffer, size_t size) 21 | : ptr_(buffer), size_(size) { 22 | } 23 | 24 | void write(char ch) noexcept { 25 | if (size_ > 1) { 26 | *ptr_++ = ch; 27 | --size_; 28 | } 29 | ++written; 30 | } 31 | 32 | void write(const char *p, size_t n) { 33 | const size_t count = std::min(size_, n); 34 | std::memcpy(ptr_, p, count); 35 | size_ -= count; 36 | written += count; 37 | } 38 | 39 | void done() noexcept { 40 | if (size_ != 0) { 41 | *ptr_ = '\0'; 42 | } 43 | } 44 | 45 | char *ptr_; 46 | size_t size_; 47 | size_t written = 0; 48 | }; 49 | 50 | // This context writes to a container using a std::back_inserter 51 | struct ostream_writer { 52 | 53 | ostream_writer(std::ostream &os) 54 | : os_(os) { 55 | } 56 | 57 | void write(char ch) { 58 | os_.put(ch); 59 | ++written; 60 | } 61 | 62 | void write(const char *p, size_t n) { 63 | os_.write(p, n); 64 | written += n; 65 | } 66 | 67 | void done() noexcept {} 68 | 69 | std::ostream &os_; 70 | size_t written = 0; 71 | }; 72 | 73 | // This context writes to a container using a std::back_inserter 74 | template 75 | struct container_writer { 76 | 77 | container_writer(C &s) 78 | : it_(std::back_inserter(s)) { 79 | } 80 | 81 | void write(char ch) { 82 | *it_++ = ch; 83 | ++written; 84 | } 85 | 86 | void write(const char *p, size_t n) { 87 | while (n--) { 88 | write(*p++); 89 | } 90 | } 91 | 92 | void done() noexcept {} 93 | 94 | std::back_insert_iterator it_; 95 | size_t written = 0; 96 | }; 97 | 98 | // this context writes to an STDIO stream 99 | struct stdio_writer { 100 | 101 | stdio_writer(FILE *stream) 102 | : stream_(stream) { 103 | } 104 | 105 | void write(char ch) noexcept { 106 | putc(ch, stream_); 107 | ++written; 108 | } 109 | 110 | void write(const char *p, size_t n) { 111 | written += fwrite(p, n, 1, stream_); 112 | } 113 | 114 | void done() noexcept {} 115 | 116 | FILE *stream_; 117 | size_t written = 0; 118 | }; 119 | 120 | // this context writes to the stdout stream 121 | struct stdout_writer { 122 | 123 | void write(char ch) noexcept { 124 | putchar(ch); 125 | ++written; 126 | } 127 | 128 | void write(const char *p, size_t n) { 129 | written += fwrite(p, n, 1, stdout); 130 | } 131 | 132 | void done() noexcept {} 133 | 134 | size_t written = 0; 135 | }; 136 | 137 | } 138 | 139 | #endif 140 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This project is dual licensed under the BSD 3-Clause License and under the Apache License version 2.0 2 | 3 | -------------------------------------------------------------------------------- 4 | BSD 3-Clause License 5 | 6 | Copyright (c) 2019, Evan Teran 7 | All rights reserved. 8 | 9 | Redistribution and use in source and binary forms, with or without 10 | modification, are permitted provided that the following conditions are met: 11 | 12 | * Redistributions of source code must retain the above copyright notice, this 13 | list of conditions and the following disclaimer. 14 | 15 | * Redistributions in binary form must reproduce the above copyright notice, 16 | this list of conditions and the following disclaimer in the documentation 17 | and/or other materials provided with the distribution. 18 | 19 | * Neither the name of the copyright holder nor the names of its 20 | contributors may be used to endorse or promote products derived from 21 | this software without specific prior written permission. 22 | 23 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 24 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 26 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 27 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 29 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 31 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 32 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | -------------------------------------------------------------------------------- 34 | Copyright 2019 Evan Teran 35 | 36 | Licensed under the Apache License, Version 2.0 (the "License"); 37 | you may not use this file except in compliance with the License. 38 | You may obtain a copy of the License at 39 | 40 | http://www.apache.org/licenses/LICENSE-2.0 41 | 42 | Unless required by applicable law or agreed to in writing, software 43 | distributed under the License is distributed on an "AS IS" BASIS, 44 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 45 | See the License for the specific language governing permissions and 46 | limitations under the License. 47 | -------------------------------------------------------------------------------- /Printf.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef PRINTF_20160922_H_ 3 | #define PRINTF_20160922_H_ 4 | 5 | #include "Formatters.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | // #define CXX17_PRINTF_EXTENSIONS 15 | 16 | #ifdef __GNUC__ 17 | #define LIKELY(expr) __builtin_expect(!!(expr), 1) 18 | #define UNLIKELY(expr) __builtin_expect(!!(expr), 0) 19 | #define NO_INLINE __attribute__((noinline)) 20 | #else 21 | #define LIKELY(expr) (expr) 22 | #define UNLIKELY(expr) (expr) 23 | #define NO_INLINE 24 | #endif 25 | 26 | namespace cxx17 { 27 | 28 | struct format_error : std::runtime_error { 29 | format_error(const char *what_arg) 30 | : std::runtime_error(what_arg){}; 31 | }; 32 | 33 | namespace detail { 34 | 35 | enum class Modifiers : uint8_t { 36 | None, 37 | Char, 38 | Short, 39 | Long, 40 | LongLong, 41 | LongDouble, 42 | IntMaxT, 43 | SizeT, 44 | PtrDiffT 45 | }; 46 | 47 | struct Flags { 48 | uint8_t justify : 1; 49 | uint8_t sign : 1; 50 | uint8_t space : 1; 51 | uint8_t prefix : 1; 52 | uint8_t padding : 1; 53 | uint8_t reserved : 3; 54 | }; 55 | 56 | static_assert(sizeof(Flags) == sizeof(uint8_t)); 57 | 58 | [[noreturn]] 59 | // NOTE(eteran): exception throwing is considered the "cold path" 60 | // so let's never inline it to keep the hot path small 61 | void NO_INLINE ThrowError(const char *what) { 62 | throw format_error(what); 63 | } 64 | 65 | template 66 | std::tuple format(char (&buf)[N], T d, int width, Flags flags) { 67 | 68 | if constexpr (Divisor == 10) { 69 | static constexpr const char digit_pairs[201] = {"00010203040506070809" 70 | "10111213141516171819" 71 | "20212223242526272829" 72 | "30313233343536373839" 73 | "40414243444546474849" 74 | "50515253545556575859" 75 | "60616263646566676869" 76 | "70717273747576777879" 77 | "80818283848586878889" 78 | "90919293949596979899"}; 79 | 80 | char *it = &buf[N - 2]; 81 | if constexpr (std::is_signed::value) { 82 | if (d >= 0) { 83 | int div = d / 100; 84 | while (div) { 85 | std::memcpy(it, &digit_pairs[2 * (d - div * 100)], 2); 86 | d = div; 87 | it -= 2; 88 | div = d / 100; 89 | } 90 | 91 | std::memcpy(it, &digit_pairs[2 * d], 2); 92 | 93 | if (d < 10) { 94 | it++; 95 | } 96 | 97 | if (flags.space) { 98 | if (flags.padding) { 99 | while (&buf[N] - it < width - 1) { 100 | *--it = '0'; 101 | } 102 | } 103 | *--it = ' '; 104 | } else if (flags.sign) { 105 | if (flags.padding) { 106 | while (&buf[N] - it < width - 1) { 107 | *--it = '0'; 108 | } 109 | } 110 | *--it = '+'; 111 | 112 | } else { 113 | if (flags.padding) { 114 | while (&buf[N] - it < width) { 115 | *--it = '0'; 116 | } 117 | } 118 | } 119 | 120 | } else { 121 | int div = d / 100; 122 | while (div) { 123 | std::memcpy(it, &digit_pairs[-2 * (d - div * 100)], 2); 124 | d = div; 125 | it -= 2; 126 | div = d / 100; 127 | } 128 | 129 | std::memcpy(it, &digit_pairs[-2 * d], 2); 130 | 131 | if (d <= -10) { 132 | it--; 133 | } 134 | 135 | if (flags.padding) { 136 | while (&buf[N] - it < width) { 137 | *--it = '0'; 138 | } 139 | } 140 | 141 | *it = '-'; 142 | } 143 | } else { 144 | if (d >= 0) { 145 | int div = d / 100; 146 | while (div) { 147 | std::memcpy(it, &digit_pairs[2 * (d - div * 100)], 2); 148 | d = div; 149 | it -= 2; 150 | div = d / 100; 151 | } 152 | 153 | std::memcpy(it, &digit_pairs[2 * d], 2); 154 | 155 | if (d < 10) { 156 | it++; 157 | } 158 | 159 | if (flags.space) { 160 | if (flags.padding) { 161 | while (&buf[N] - it < width - 1) { 162 | *--it = '0'; 163 | } 164 | } 165 | *--it = ' '; 166 | } else if (flags.sign) { 167 | if (flags.padding) { 168 | while (&buf[N] - it < width - 1) { 169 | *--it = '0'; 170 | } 171 | } 172 | *--it = '+'; 173 | 174 | } else { 175 | if (flags.padding) { 176 | while (&buf[N] - it < width) { 177 | *--it = '0'; 178 | } 179 | } 180 | } 181 | } 182 | } 183 | 184 | return std::make_tuple(it, &buf[N] - it); 185 | } else if constexpr (Divisor == 16) { 186 | [[maybe_unused]] static constexpr const char xdigit_pairs_l[513] = {"000102030405060708090a0b0c0d0e0f" 187 | "101112131415161718191a1b1c1d1e1f" 188 | "202122232425262728292a2b2c2d2e2f" 189 | "303132333435363738393a3b3c3d3e3f" 190 | "404142434445464748494a4b4c4d4e4f" 191 | "505152535455565758595a5b5c5d5e5f" 192 | "606162636465666768696a6b6c6d6e6f" 193 | "707172737475767778797a7b7c7d7e7f" 194 | "808182838485868788898a8b8c8d8e8f" 195 | "909192939495969798999a9b9c9d9e9f" 196 | "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf" 197 | "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" 198 | "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf" 199 | "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf" 200 | "e0e1e2e3e4e5e6e7e8e9eaebecedeeef" 201 | "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"}; 202 | 203 | [[maybe_unused]] static constexpr const char xdigit_pairs_u[513] = {"000102030405060708090A0B0C0D0E0F" 204 | "101112131415161718191A1B1C1D1E1F" 205 | "202122232425262728292A2B2C2D2E2F" 206 | "303132333435363738393A3B3C3D3E3F" 207 | "404142434445464748494A4B4C4D4E4F" 208 | "505152535455565758595A5B5C5D5E5F" 209 | "606162636465666768696A6B6C6D6E6F" 210 | "707172737475767778797A7B7C7D7E7F" 211 | "808182838485868788898A8B8C8D8E8F" 212 | "909192939495969798999A9B9C9D9E9F" 213 | "A0A1A2A3A4A5A6A7A8A9AAABACADAEAF" 214 | "B0B1B2B3B4B5B6B7B8B9BABBBCBDBEBF" 215 | "C0C1C2C3C4C5C6C7C8C9CACBCCCDCECF" 216 | "D0D1D2D3D4D5D6D7D8D9DADBDCDDDEDF" 217 | "E0E1E2E3E4E5E6E7E8E9EAEBECEDEEEF" 218 | "F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF"}; 219 | 220 | // NOTE(eteran): we include the x/X, here as an easy way to put the 221 | // upper/lower case prefix for hex numbers 222 | [[maybe_unused]] static constexpr const char alphabet_l[] = "0123456789abcdefx"; 223 | [[maybe_unused]] static constexpr const char alphabet_u[] = "0123456789ABCDEFX"; 224 | 225 | typename std::make_unsigned::type ud = d; 226 | 227 | char *p = buf + N; 228 | 229 | if (ud >= 0) { 230 | while (ud > 16) { 231 | p -= 2; 232 | if constexpr (Upper) { 233 | std::memcpy(p, &xdigit_pairs_u[2 * (ud & 0xff)], 2); 234 | } else { 235 | std::memcpy(p, &xdigit_pairs_l[2 * (ud & 0xff)], 2); 236 | } 237 | ud /= 256; 238 | } 239 | 240 | while (ud > 0) { 241 | p -= 1; 242 | if constexpr (Upper) { 243 | std::memcpy(p, &xdigit_pairs_u[2 * (ud & 0x0f) + 1], 1); 244 | } else { 245 | std::memcpy(p, &xdigit_pairs_l[2 * (ud & 0x0f) + 1], 1); 246 | } 247 | ud /= 16; 248 | } 249 | 250 | // add in any necessary padding 251 | if (flags.padding) { 252 | while (&buf[N] - p < width) { 253 | *--p = '0'; 254 | } 255 | } 256 | 257 | // add the prefix as needed 258 | if (flags.prefix) { 259 | if constexpr (Upper) { 260 | *--p = alphabet_u[16]; 261 | } else { 262 | *--p = alphabet_l[16]; 263 | } 264 | *--p = '0'; 265 | } 266 | } 267 | 268 | return std::make_tuple(p, (buf + N) - p); 269 | } else if constexpr (Divisor == 8) { 270 | static constexpr const char digit_pairs[129] = {"0001020304050607" 271 | "1011121314151617" 272 | "2021222324252627" 273 | "3031323334353637" 274 | "4041424344454647" 275 | "5051525354555657" 276 | "6061626364656667" 277 | "7071727374757677"}; 278 | typename std::make_unsigned::type ud = d; 279 | 280 | char *p = buf + N; 281 | 282 | if (ud >= 0) { 283 | while (ud > 64) { 284 | p -= 2; 285 | std::memcpy(p, &digit_pairs[2 * (ud & 077)], 2); 286 | ud /= 64; 287 | } 288 | 289 | while (ud > 0) { 290 | p -= 1; 291 | std::memcpy(p, &digit_pairs[2 * (ud & 007) + 1], 1); 292 | ud /= 8; 293 | } 294 | 295 | // add in any necessary padding 296 | if (flags.padding) { 297 | while (&buf[N] - p < width) { 298 | *--p = '0'; 299 | } 300 | } 301 | 302 | // add the prefix as needed 303 | if (flags.prefix) { 304 | *--p = '0'; 305 | } 306 | } 307 | 308 | return std::make_tuple(p, (buf + N) - p); 309 | } else if constexpr (Divisor == 2) { 310 | static constexpr const char digit_pairs[9] = {"00011011"}; 311 | 312 | typename std::make_unsigned::type ud = d; 313 | 314 | char *p = buf + N; 315 | 316 | if (ud >= 0) { 317 | while (ud > 4) { 318 | p -= 2; 319 | std::memcpy(p, &digit_pairs[2 * (ud & 0x03)], 2); 320 | ud /= 4; 321 | } 322 | 323 | while (ud > 0) { 324 | p -= 1; 325 | std::memcpy(p, &digit_pairs[2 * (ud & 0x01) + 1], 1); 326 | ud /= 2; 327 | } 328 | 329 | // add in any necessary padding 330 | if (flags.padding) { 331 | while (&buf[N] - p < width) { 332 | *--p = '0'; 333 | } 334 | } 335 | 336 | // add the prefix as needed 337 | if (flags.prefix) { 338 | *--p = '0'; 339 | } 340 | } 341 | 342 | return std::make_tuple(p, (buf + N) - p); 343 | } 344 | 345 | ThrowError("Invalid Base Used In Integer To String Conversion"); 346 | } 347 | 348 | //------------------------------------------------------------------------------ 349 | // Name: itoa 350 | // Desc: as a minor optimization, let's determine a few things up front and pass 351 | // them as template parameters enabling some more aggressive optimizations 352 | // when the division can use more efficient operations 353 | //------------------------------------------------------------------------------ 354 | template 355 | std::tuple itoa(char (&buf)[N], char base, int precision, T d, int width, Flags flags) { 356 | 357 | if (d == 0 && precision == 0) { 358 | *buf = '\0'; 359 | return std::make_tuple(buf, 0); 360 | } 361 | 362 | switch (base) { 363 | case 'i': 364 | case 'd': 365 | case 'u': 366 | return format(buf, d, width, flags); 367 | #ifdef CXX17_PRINTF_EXTENSIONS 368 | case 'b': 369 | return format(buf, d, width, flags); 370 | #endif 371 | case 'X': 372 | return format(buf, d, width, flags); 373 | case 'x': 374 | return format(buf, d, width, flags); 375 | case 'o': 376 | return format(buf, d, width, flags); 377 | default: 378 | return format(buf, d, width, flags); 379 | } 380 | } 381 | 382 | //------------------------------------------------------------------------------ 383 | // Name: output_string 384 | // Desc: prints a string to the Context object, taking into account padding flags 385 | // Note: ch is the current format specifier 386 | //------------------------------------------------------------------------------ 387 | template 388 | void output_string(char ch, const char *s_ptr, int precision, long int width, Flags flags, int len, Context &ctx) noexcept { 389 | 390 | if ((ch == 's' && precision >= 0 && precision < len)) { 391 | len = precision; 392 | } 393 | 394 | // if not left justified padding goes first... 395 | if (!flags.justify) { 396 | // spaces go before the prefix... 397 | while (width-- > len) { 398 | ctx.write(' '); 399 | } 400 | } 401 | 402 | // output the string 403 | // NOTE(eteran): len is at most strlen, possibly is less 404 | // so we can write len chars 405 | width -= len; 406 | 407 | ctx.write(s_ptr, len); 408 | 409 | // if left justified padding goes last... 410 | if (flags.justify) { 411 | while (width-- > 0) { 412 | ctx.write(' '); 413 | } 414 | } 415 | } 416 | 417 | // NOTE(eteran): Here is some code to fetch arguments of specific types. 418 | 419 | #ifdef CXX17_PRINTF_EXTENSIONS 420 | std::string formatted_object(std::string obj) { 421 | return obj; 422 | } 423 | 424 | template 425 | std::string to_string(T) { 426 | ThrowError("No to_string found for this object type"); 427 | } 428 | 429 | template 430 | std::string formatted_object(T obj) { 431 | using detail::to_string; 432 | using std::to_string; 433 | return to_string(obj); 434 | } 435 | #endif 436 | 437 | template 438 | constexpr const char *formatted_string([[maybe_unused]] T s) { 439 | if constexpr (std::is_convertible::value) { 440 | return static_cast(s); 441 | } 442 | ThrowError("Non-String Argument For String Format"); 443 | } 444 | 445 | template 446 | constexpr R formatted_pointer([[maybe_unused]] T p) { 447 | if constexpr (std::is_convertible::value) { 448 | return reinterpret_cast(reinterpret_cast(p)); 449 | } 450 | ThrowError("Non-Pointer Argument For Pointer Format"); 451 | } 452 | 453 | template 454 | constexpr R formatted_integer([[maybe_unused]] T n) { 455 | if constexpr (std::is_integral::value) { 456 | return static_cast(n); 457 | } 458 | ThrowError("Non-Integer Argument For Integer Format"); 459 | } 460 | 461 | //------------------------------------------------------------------------------ 462 | // Name: process_format 463 | // Desc: prints the next argument to the Context taking into account the flags, 464 | // width, precision, and modifiers collected along the way. Then will 465 | // recursively continue processing the string 466 | //------------------------------------------------------------------------------ 467 | template 468 | int process_format(Context &ctx, const char *format, Flags flags, long int width, long int precision, Modifiers modifier, const T &arg, const Ts &...ts) { 469 | 470 | // enough to contain a 64-bit number in bin notation + optional prefix 471 | char num_buf[67]; 472 | 473 | size_t slen; 474 | const char *s_ptr; 475 | 476 | char ch = *format; 477 | switch (ch) { 478 | case 'e': 479 | case 'E': 480 | case 'f': 481 | case 'F': 482 | case 'a': 483 | case 'A': 484 | case 'g': 485 | case 'G': 486 | // TODO(eteran): implement float formatting... for now, just consume the argument 487 | return Printf(ctx, format + 1, ts...); 488 | 489 | case 'p': 490 | precision = 1; 491 | ch = 'x'; 492 | flags.prefix = 1; 493 | 494 | // NOTE(eteran): GNU printf prints "(nil)" for NULL pointers, we print 0x0 495 | std::tie(s_ptr, slen) = itoa(num_buf, ch, precision, formatted_pointer(arg), width, flags); 496 | 497 | output_string(ch, s_ptr, precision, width, flags, slen, ctx); 498 | return Printf(ctx, format + 1, ts...); 499 | case 'x': 500 | case 'X': 501 | case 'u': 502 | case 'o': 503 | #ifdef CXX17_PRINTF_EXTENSIONS 504 | case 'b': // extension, BINARY mode 505 | #endif 506 | if (precision < 0) { 507 | precision = 1; 508 | } 509 | 510 | switch (modifier) { 511 | case Modifiers::Char: 512 | std::tie(s_ptr, slen) = itoa(num_buf, ch, precision, formatted_integer(arg), width, flags); 513 | break; 514 | case Modifiers::Short: 515 | std::tie(s_ptr, slen) = itoa(num_buf, ch, precision, formatted_integer(arg), width, flags); 516 | break; 517 | case Modifiers::Long: 518 | std::tie(s_ptr, slen) = itoa(num_buf, ch, precision, formatted_integer(arg), width, flags); 519 | break; 520 | case Modifiers::LongLong: 521 | std::tie(s_ptr, slen) = itoa(num_buf, ch, precision, formatted_integer(arg), width, flags); 522 | break; 523 | case Modifiers::IntMaxT: 524 | std::tie(s_ptr, slen) = itoa(num_buf, ch, precision, formatted_integer(arg), width, flags); 525 | break; 526 | case Modifiers::SizeT: 527 | std::tie(s_ptr, slen) = itoa(num_buf, ch, precision, formatted_integer(arg), width, flags); 528 | break; 529 | case Modifiers::PtrDiffT: 530 | std::tie(s_ptr, slen) = itoa(num_buf, ch, precision, formatted_integer::type>(arg), width, flags); 531 | break; 532 | default: 533 | std::tie(s_ptr, slen) = itoa(num_buf, ch, precision, formatted_integer(arg), width, flags); 534 | break; 535 | } 536 | 537 | output_string(ch, s_ptr, precision, width, flags, slen, ctx); 538 | return Printf(ctx, format + 1, ts...); 539 | 540 | case 'i': 541 | case 'd': 542 | if (precision < 0) { 543 | precision = 1; 544 | } 545 | 546 | switch (modifier) { 547 | case Modifiers::Char: 548 | std::tie(s_ptr, slen) = itoa(num_buf, ch, precision, formatted_integer(arg), width, flags); 549 | break; 550 | case Modifiers::Short: 551 | std::tie(s_ptr, slen) = itoa(num_buf, ch, precision, formatted_integer(arg), width, flags); 552 | break; 553 | case Modifiers::Long: 554 | std::tie(s_ptr, slen) = itoa(num_buf, ch, precision, formatted_integer(arg), width, flags); 555 | break; 556 | case Modifiers::LongLong: 557 | std::tie(s_ptr, slen) = itoa(num_buf, ch, precision, formatted_integer(arg), width, flags); 558 | break; 559 | case Modifiers::IntMaxT: 560 | std::tie(s_ptr, slen) = itoa(num_buf, ch, precision, formatted_integer(arg), width, flags); 561 | break; 562 | case Modifiers::SizeT: 563 | std::tie(s_ptr, slen) = itoa(num_buf, ch, precision, formatted_integer::type>(arg), width, flags); 564 | break; 565 | case Modifiers::PtrDiffT: 566 | std::tie(s_ptr, slen) = itoa(num_buf, ch, precision, formatted_integer(arg), width, flags); 567 | break; 568 | default: 569 | std::tie(s_ptr, slen) = itoa(num_buf, ch, precision, formatted_integer(arg), width, flags); 570 | break; 571 | } 572 | 573 | output_string(ch, s_ptr, precision, width, flags, slen, ctx); 574 | return Printf(ctx, format + 1, ts...); 575 | 576 | case 'c': 577 | // char is promoted to an int when pushed on the stack 578 | num_buf[0] = formatted_integer(arg); 579 | num_buf[1] = '\0'; 580 | s_ptr = num_buf; 581 | output_string('c', s_ptr, precision, width, flags, 1, ctx); 582 | return Printf(ctx, format + 1, ts...); 583 | 584 | case 's': 585 | s_ptr = formatted_string(arg); 586 | if (!s_ptr) { 587 | s_ptr = "(null)"; 588 | } 589 | output_string('s', s_ptr, precision, width, flags, strlen(s_ptr), ctx); 590 | return Printf(ctx, format + 1, ts...); 591 | 592 | #ifdef CXX17_PRINTF_EXTENSIONS 593 | case '?': { 594 | std::string s = formatted_object(arg); 595 | output_string('s', s.data(), precision, width, flags, s.size(), ctx); 596 | } 597 | return Printf(ctx, format + 1, ts...); 598 | #endif 599 | 600 | case 'n': 601 | switch (modifier) { 602 | case Modifiers::Char: 603 | *formatted_pointer(arg) = ctx.written; 604 | break; 605 | case Modifiers::Short: 606 | *formatted_pointer(arg) = ctx.written; 607 | break; 608 | case Modifiers::Long: 609 | *formatted_pointer(arg) = ctx.written; 610 | break; 611 | case Modifiers::LongLong: 612 | *formatted_pointer(arg) = ctx.written; 613 | break; 614 | case Modifiers::IntMaxT: 615 | *formatted_pointer(arg) = ctx.written; 616 | break; 617 | case Modifiers::SizeT: 618 | *formatted_pointer::type *>(arg) = ctx.written; 619 | break; 620 | case Modifiers::PtrDiffT: 621 | *formatted_pointer(arg) = ctx.written; 622 | break; 623 | default: 624 | *formatted_pointer(arg) = ctx.written; 625 | break; 626 | } 627 | 628 | return Printf(ctx, format + 1, ts...); 629 | 630 | default: 631 | ctx.write('%'); 632 | [[fallthrough]]; 633 | case '\0': 634 | case '%': 635 | ctx.write(ch); 636 | break; 637 | } 638 | 639 | return Printf(ctx, format + 1, ts...); 640 | } 641 | 642 | //------------------------------------------------------------------------------ 643 | // Name: get_modifier 644 | // Desc: gets the modifier, if any, from the format string, then calls 645 | // process_format 646 | //------------------------------------------------------------------------------ 647 | template 648 | int get_modifier(Context &ctx, const char *format, Flags flags, long int width, long int precision, const T &arg, const Ts &...ts) { 649 | 650 | Modifiers modifier = Modifiers::None; 651 | 652 | switch (*format) { 653 | case 'h': 654 | modifier = Modifiers::Short; 655 | ++format; 656 | if (*format == 'h') { 657 | modifier = Modifiers::Char; 658 | ++format; 659 | } 660 | break; 661 | case 'l': 662 | modifier = Modifiers::Long; 663 | ++format; 664 | if (*format == 'l') { 665 | modifier = Modifiers::LongLong; 666 | ++format; 667 | } 668 | break; 669 | case 'L': 670 | modifier = Modifiers::LongDouble; 671 | ++format; 672 | break; 673 | case 'j': 674 | modifier = Modifiers::IntMaxT; 675 | ++format; 676 | break; 677 | case 'z': 678 | modifier = Modifiers::SizeT; 679 | ++format; 680 | break; 681 | case 't': 682 | modifier = Modifiers::PtrDiffT; 683 | ++format; 684 | break; 685 | default: 686 | break; 687 | } 688 | 689 | return process_format(ctx, format, flags, width, precision, modifier, arg, ts...); 690 | } 691 | 692 | //------------------------------------------------------------------------------ 693 | // Name: get_precision 694 | // Desc: gets the precision, if any, either from the format string or as an arg 695 | // as needed, then calls get_modifier 696 | //------------------------------------------------------------------------------ 697 | template 698 | int get_precision(Context &ctx, const char *format, Flags flags, long int width, const T &arg, const Ts &...ts) { 699 | 700 | // default to non-existant 701 | long int p = -1; 702 | 703 | if (*format == '.') { 704 | 705 | ++format; 706 | if (*format == '*') { 707 | ++format; 708 | // pull an int off the stack for processing 709 | p = formatted_integer(arg); 710 | if constexpr (sizeof...(ts) > 0) { 711 | return get_modifier(ctx, format, flags, width, p, ts...); 712 | } 713 | ThrowError("Internal Error"); 714 | } else { 715 | char *endptr; 716 | p = std::strtol(format, &endptr, 10); 717 | format = endptr; 718 | return get_modifier(ctx, format, flags, width, p, arg, ts...); 719 | } 720 | } 721 | 722 | return get_modifier(ctx, format, flags, width, p, arg, ts...); 723 | } 724 | 725 | //------------------------------------------------------------------------------ 726 | // Name: get_width 727 | // Desc: gets the width if any, either from the format string or as an arg as 728 | // needed, then calls get_precision 729 | //------------------------------------------------------------------------------ 730 | template 731 | int get_width(Context &ctx, const char *format, Flags flags, const T &arg, const Ts &...ts) { 732 | 733 | int width = 0; 734 | 735 | if (*format == '*') { 736 | ++format; 737 | // pull an int off the stack for processing 738 | width = formatted_integer(arg); 739 | if constexpr (sizeof...(ts) > 0) { 740 | return get_precision(ctx, format, flags, width, ts...); 741 | } 742 | ThrowError("Internal Error"); 743 | } else { 744 | char *endptr; 745 | width = std::strtol(format, &endptr, 10); 746 | format = endptr; 747 | return get_precision(ctx, format, flags, width, arg, ts...); 748 | } 749 | } 750 | 751 | //------------------------------------------------------------------------------ 752 | // Name: get_flags 753 | // Desc: gets the flags, if any, from the format string, then calls get_width 754 | //------------------------------------------------------------------------------ 755 | template 756 | int get_flags(Context &ctx, const char *format, const Ts &...ts) { 757 | 758 | Flags f = {0, 0, 0, 0, 0, 0}; 759 | bool done = false; 760 | 761 | // skip past the % char 762 | ++format; 763 | do { 764 | switch (*format) { 765 | case '-': 766 | // justify, overrides padding 767 | f.justify = 1; 768 | f.padding = 0; 769 | ++format; 770 | break; 771 | case '+': 772 | // sign, overrides space 773 | f.sign = 1; 774 | f.space = 0; 775 | ++format; 776 | break; 777 | case ' ': 778 | if (!f.sign) { 779 | f.space = 1; 780 | } 781 | ++format; 782 | break; 783 | case '#': 784 | f.prefix = 1; 785 | ++format; 786 | break; 787 | case '0': 788 | if (!f.justify) { 789 | f.padding = 1; 790 | } 791 | ++format; 792 | break; 793 | default: 794 | done = true; 795 | } 796 | } while (!done); 797 | 798 | return get_width(ctx, format, f, ts...); 799 | } 800 | 801 | } 802 | 803 | //------------------------------------------------------------------------------ 804 | // Name: Printf 805 | //------------------------------------------------------------------------------ 806 | template 807 | int Printf(Context &ctx, const char *format, const Ts &...ts) { 808 | 809 | assert(format); 810 | 811 | if constexpr (sizeof...(ts) > 0) { 812 | while (LIKELY(*format != '\0')) { 813 | if (LIKELY(*format == '%')) { 814 | // %[flag][width][.precision][length]char 815 | 816 | // this recurses into get_width -> get_precision -> get_length -> process_format 817 | return detail::get_flags(ctx, format, ts...); 818 | } else { 819 | // do long strips of non-format chars in bulk 820 | const char *first = format; 821 | size_t count = 0; 822 | do { 823 | ++format; 824 | ++count; 825 | } while (UNLIKELY(*format != '%')); 826 | 827 | ctx.write(first, count); 828 | continue; 829 | } 830 | 831 | ++format; 832 | } 833 | 834 | // clean up any trailing stuff 835 | return Printf(ctx, format + 1, ts...); 836 | } else { 837 | for (; *format; ++format) { 838 | if (LIKELY(*format != '%' || *++format == '%')) { 839 | ctx.write(*format); 840 | continue; 841 | } 842 | 843 | detail::ThrowError("Bad Format"); 844 | } 845 | 846 | // this will usually null terminate the string 847 | ctx.done(); 848 | 849 | // return the amount of bytes that should have been written if there was sufficient space 850 | return ctx.written; 851 | } 852 | } 853 | 854 | //------------------------------------------------------------------------------ 855 | // Name: snprintf 856 | // Desc: implementation of a snprintf compatible interface 857 | //------------------------------------------------------------------------------ 858 | template 859 | int sprintf(std::ostream &os, const char *format, const Ts &...ts) { 860 | ostream_writer ctx(os); 861 | return Printf(ctx, format, ts...); 862 | } 863 | 864 | //------------------------------------------------------------------------------ 865 | // Name: snprintf 866 | // Desc: implementation of a snprintf compatible interface, fixed bufer size safe! 867 | //------------------------------------------------------------------------------ 868 | template 869 | int sprintf(char (&buffer)[N], const char *format, const Ts &...ts) { 870 | buffer_writer ctx(buffer); 871 | return Printf(ctx, format, ts...); 872 | } 873 | 874 | //------------------------------------------------------------------------------ 875 | // Name: sprintf 876 | // Desc: implementation of a s[n]printf compatible interface 877 | //------------------------------------------------------------------------------ 878 | template 879 | int sprintf(char *str, size_t size, const char *format, const Ts &...ts) { 880 | buffer_writer ctx(str, size); 881 | return Printf(ctx, format, ts...); 882 | } 883 | 884 | //------------------------------------------------------------------------------ 885 | // Name: printf 886 | // Desc: implementation of a printf compatible interface 887 | //------------------------------------------------------------------------------ 888 | template 889 | int printf(const char *format, const Ts &...ts) { 890 | stdout_writer ctx; 891 | return Printf(ctx, format, ts...); 892 | } 893 | 894 | } 895 | 896 | #undef LIKELY 897 | #undef UNLIKELY 898 | 899 | #endif 900 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cxx17_printf 2 | 3 | An implementation of `printf` using c++17's variadic templates. It was inspired 4 | by some of the "safe printf" examples that I've seen. But none of them attempted 5 | to actually implement the `printf` fully with all of its quirks. This code is 6 | (an attempt) to do that. 7 | 8 | An advantage of this, is that we can support ANY type being passed to a format, 9 | even complex objects. For example, when `CXX17_PRINTF_EXTENSIONS` is enabled 10 | `"%?"` means "call `to_string` on the argument and print the result" This uses 11 | ADL, so it will find the version that you specify in your namespaces, or use 12 | `std::to_string` as a fallback. If no `to_string` is found, it uses the internal 13 | one which asserts. 14 | 15 | NOTE: floating point is not implemented, as it is complex to do correctly, but 16 | is on the TODO list. 17 | 18 | Usage is similar to `snprintf`, but more robust. Instead of a buffer/size pair 19 | being passed as a parameter, you pass a context object which has 3 functions 20 | and a data member: 21 | 22 | // writes a single character to the output of your choosing 23 | void write(char ch); 24 | 25 | // writes a string of characters to the output of your choosing 26 | void write(const char *p, size_t n); 27 | 28 | // called when formatting is complete, useful for ensuring NUL termination 29 | void done(); 30 | 31 | // increment this every time we write is called, this is what printf will 32 | // return. 33 | size_t written = 0; 34 | 35 | 36 | A simple example of the usage of the library is as follows: 37 | 38 | char buf[256]; 39 | cxx17::buffer_writer ctx(buf, sizeof(buf)); 40 | cxx17::Printf(ctx, "[hello %*s %d]", 10, "world", 123); 41 | 42 | // buffer now contains "[hello world 123]" 43 | 44 | The context uses duck typing, so any object that meets the critera will suffice, 45 | but there are several examples in Formatters.h 46 | 47 | -------- 48 | 49 | Additionally, while the context based interface is very flexible and can 50 | accomidate essentially any destination stream or buffer. This implementation 51 | also includes more familiar interfaces: 52 | 53 | * `int cxx17::sprintf(std::ostream &os, const char *format, const Ts &... ts);` 54 | * `int cxx17::sprintf(char *str, size_t size, const char *format, const Ts &... ts);` 55 | * `int cxx17::printf(const char *format, const Ts &... ts);` 56 | 57 | All of which work in the expected ways without the need to manually manage the 58 | concept of "contexts". 59 | 60 | 61 | -------- 62 | 63 | Performance so far, when optimizations are at -O3, slightly edges out glibc's 64 | printf. Here is the included test program's output on my machine: 65 | 66 | hello world, A, -123, 00001234 0x7ffff03cb7c8 0000004294967292 ffffffff 0000000000000100 67 | hello world, A, -123, 00001234 0x7ffff03cb7c8 0000004294967292 ffffffff 0000000000000100 68 | cxx17 Took: 366344 µs to execute. 69 | printf Took: 384387 µs to execute. 70 | 71 | 72 | When profile guided optimizations are used, it performs even better. I am sure 73 | however, that there is room for some optimizations too :-) 74 | -------------------------------------------------------------------------------- /Test.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "Printf.h" 3 | 4 | #include 5 | #include 6 | 7 | template 8 | R time_code(F func) { 9 | 10 | // test the timing of our printf 11 | auto then = std::chrono::system_clock::now(); 12 | 13 | for (int i = 0; i < Count; ++i) { 14 | func(); 15 | } 16 | 17 | auto now = std::chrono::system_clock::now(); 18 | auto dur = now - then; 19 | 20 | return std::chrono::duration_cast(dur); 21 | } 22 | 23 | #ifdef CXX17_PRINTF_EXTENSIONS 24 | class Test {}; 25 | 26 | std::string to_string(Test) { 27 | return "Test!"; 28 | } 29 | #endif 30 | 31 | int main() { 32 | int Foo = 1234; 33 | 34 | // first test correctness 35 | cxx17::printf("hello %*s, %c, %d, %08x %p %016u %02x %016o\n", 10, "world", 0x41, -123, 0x1234, static_cast(&Foo), -4, -1, 64); 36 | printf("hello %*s, %c, %d, %08x %p %016u %02x %016o\n", 10, "world", 0x41, -123, 0x1234, static_cast(&Foo), -4, -1, 64); 37 | 38 | using ms = std::chrono::microseconds; 39 | 40 | constexpr int count = 800000; 41 | 42 | auto time1 = time_code([&Foo]() { 43 | char buf[128]; 44 | cxx17::sprintf(buf, "hello %*s, %c, %d, %08x %p %016u %02x %016o\n", 10, "world", 0x41, -123, 0x1234, static_cast(&Foo), -4, -1, 1234); 45 | }); 46 | 47 | auto time2 = time_code([&Foo]() { 48 | char buf[128]; 49 | snprintf(buf, sizeof(buf), "hello %*s, %c, %d, %08x %p %016u %02x %016o\n", 10, "world", 0x41, -123, 0x1234, static_cast(&Foo), -4, -1, 1234); 50 | }); 51 | 52 | std::cerr << "cxx17 Took: " << time1.count() << " \xC2\xB5s to execute." << std::endl; 53 | std::cerr << "printf Took: " << time2.count() << " \xC2\xB5s to execute." << std::endl; 54 | 55 | #ifdef CXX17_PRINTF_EXTENSIONS 56 | { 57 | std::string s = "[std::string]!"; 58 | cxx17::sprintf(std::cout, "hello %10? %?\n", s, Test()); 59 | 60 | cxx17::printf("%032b\n", 5ul); 61 | cxx17::printf("%032b\n", 1234ul); 62 | } 63 | #endif 64 | } 65 | --------------------------------------------------------------------------------