├── README.md ├── basic_examples.cpp ├── ggformat.cpp ├── ggformat.h └── string_examples.cpp /README.md: -------------------------------------------------------------------------------- 1 | # ggformat v1.1 2 | 3 | ggformat is a liberally licensed string formatting library for C++ that 4 | supports user defined types and sub-second compile times. It's meant to 5 | be used as a replacement for printf and friends. 6 | 7 | ggformat saves you time by reducing the amount of tedious boilerplate 8 | code you have to write, without paying it back at compile time. ggformat 9 | has a trivial API and is easy to incrementally add to an existing 10 | codebase. ggformat integrates nicely with custom strings and allocators, 11 | making it appropriate for use in long-lived applications and games where 12 | memory fragmentation is a concern. 13 | 14 | ggformat supports VS2015 onwards, GCC and clang out of the box. It 15 | should also work with VS2013 but consider it unsupported. 16 | 17 | 18 | ## Version history 19 | 20 | As of March 2021, ggformat has been used in shipped code for three and a 21 | half years with only a single minor bugfix. I consider it complete. 22 | 23 | - __v1.1 3rd Mar 2021__: fix undefined behaviour in binary printer, and 24 | some potential compilation issues. 25 | - __v1.0 29th Oct 2017__: variadic arguments are now passed by const 26 | reference. You can now use ggformat with types that have deleted copy 27 | constructors/assignment operators. 28 | 29 | 30 | ## Usage 31 | 32 | ```cpp 33 | size_t ggformat( char * buf, size_t len, const char * fmt, ... ); 34 | bool ggprint_to_file( FILE * file, const char * fmt, ... ); 35 | bool ggprint( const char * fmt, ... ); 36 | ``` 37 | 38 | In short, `ggformat` replaces `snprintf`, `ggprint_to_file` replaces 39 | `fprintf`, and `ggprint` replaces `printf`. 40 | 41 | `ggformat` writes at most `len` bytes to `buf`, and includes a null 42 | terminator unless `len` is zero. It returns the number of bytes that 43 | would have been written if `buf` were arbitrarily large, _not including 44 | the null terminator_. You may use the return value to allocate a new 45 | buffer large enough to hold the full string, see the dynamic allocation 46 | section below for an example. 47 | 48 | `ggprint_to_file` does what you would expect, and `ggprint` writes to 49 | standard output. Both return `true` on success, or `false` if the write 50 | fails. Both use `fwrite` internally and set `errno` accordingly. 51 | 52 | `ggformat` does not allocate memory. `ggprint_to_file` and `ggprint` 53 | allocate if you print lots of data. All functions abort if you mess up 54 | the formatting string or don't pass the right number of arguments. You 55 | should not pass user defined strings as format strings, and I believe 56 | it's more helpful to fail hard on programmer typos. 57 | 58 | Basic usage looks like this: 59 | 60 | ```cpp 61 | #include "ggformat.h" 62 | 63 | int main() { 64 | ggprint( "hello {}\n", 1.23 ); // hello 1.23000 65 | return 0; 66 | } 67 | ``` 68 | 69 | and you can see more examples in basic_examples.cpp and 70 | string_examples.cpp. 71 | 72 | 73 | ## Format options 74 | 75 | You can add format specifiers between the braces to change how things 76 | are printed. The following options are supported: 77 | 78 | - Plus sign (`{+}`): Prints a leading + for positive numeric types. 79 | - Width (`{#}`): left pads the output with spaces to be `#` characters 80 | wide. When used on floats, it left pads the output so the _left side 81 | of the decimal point_ is `x` characters wide. Note that this behaviour 82 | doesn't match printf, but it does make format strings like `{#.#}` 83 | behave like you would expect.. If the output is already wider than `#` 84 | characters, it doesn't do anything. 85 | - Width with zero padding (`{0#}`): as above, but pads with zeroes 86 | instead of spaces. 87 | - Width with left alignment (`{-#}` or `{-0#}`): same again but puts the 88 | spaces/zeroes on the right. 89 | - Precision (`{.#}`): specifies the number of digits that appear after 90 | the decimal point when printing floats. 91 | - Number format (`{x}` or `{b}`): prints integers in hexadecimal/binary. 92 | 93 | These can all be combined, but should be kept in the order they were 94 | just listed in. 95 | 96 | If you want to print a literal { or }, use {{ and }}. 97 | 98 | 99 | ## User defined types 100 | 101 | If you want to print your own types with ggformat, you need to define 102 | `void format( FormatBuffer * fb, T x, const FormatOpts & opts );`. 103 | `FormatBuffer` is a wrapper around a `char *` and length and its exact 104 | definition is not important. `FormatOpts` holds parsed format options 105 | and is defined as: 106 | 107 | ```cpp 108 | struct FormatOpts { 109 | enum NumberFormat { DECIMAL, HEX, BINARY }; 110 | 111 | int width = -1; 112 | int precision = -1; 113 | bool plus_sign = false; 114 | bool left_align = false; 115 | bool zero_pad = false; 116 | NumberFormat number_format = DECIMAL; 117 | }; 118 | ``` 119 | 120 | `format` implementations are typically quite simple: 121 | 122 | ```cpp 123 | #include "ggformat.h" 124 | 125 | struct v3 { 126 | explicit v3( float x_, float y_, float z_ ) { x = x_; y = y_; z = z_; } 127 | float x, y, z; 128 | }; 129 | 130 | v3 operator+( const v3 & lhs, const v3 & rhs ) { 131 | return v3( lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z ); 132 | } 133 | 134 | void format( FormatBuffer * fb, const v3 & v, const FormatOpts & opts ) { 135 | format( fb, "v3(" ); 136 | format( fb, v.x, opts ); 137 | format( fb, ", " ); 138 | format( fb, v.y, opts ); 139 | format( fb, ", " ); 140 | format( fb, v.z, opts ); 141 | format( fb, ")" ); 142 | } 143 | 144 | int main() { 145 | v3 a = v3( 1, 2, 3 ); 146 | v3 b = v3( 4, 5, 6 ); 147 | // a = v3(1.00000, 2.00000, 3.00000). b = v3( 4.00, 5.00, 6.00). 148 | // a + b = v3(+5.00000, +7.00000, +9.00000) 149 | ggprint( "a = {}. b = {2.2}.\na + b = {+}\n", a, b, a + b ); 150 | 151 | return 0; 152 | } 153 | ``` 154 | 155 | If you have a huge type and don't feel like writing a wall of `format`, 156 | see `Thing` in basic_examples.cpp. 157 | 158 | 159 | ## Dynamic allocation (std::string, asprintf, etc) 160 | 161 | `ggformat` returns the number of bytes needed to store the formatted 162 | string, which you can then use to allocate a buffer large enough to hold 163 | the formatted string. With that it's easy to integrate ggformat with 164 | your favourite dynamic string solution. For example, ggformat with 165 | std::string: 166 | 167 | ```cpp 168 | template< typename... Rest > 169 | std::string ggformat_to_string( const char * fmt, const Rest & ... rest ) { 170 | size_t space_required = ggformat( nullptr, 0, fmt, rest... ); 171 | 172 | if( space_required + 1 < space_required ) 173 | throw std::overflow_error( "formatted string is too long" ); 174 | 175 | std::string result; 176 | result.resize( space_required + 1 ); // + 1 so there's space for the null terminator... 177 | ggformat( &result[ 0 ], space_required + 1, fmt, rest... ); 178 | result.resize( space_required ); // ...and then trim it off 179 | 180 | return result; 181 | } 182 | ``` 183 | 184 | 185 | ## Other stuff 186 | 187 | Since this is C++ you can and should wrap `ggformat` in a string class 188 | to make it more convenient to use. You can see an example in 189 | string_examples.cpp. 190 | 191 | ggformat uses sprintf under the hood. sprintf can be pretty slow at 192 | runtime, but compiles quickly. 193 | 194 | ggformat has a small codebase and will probably never receive another 195 | update. If you don't like some of my decisions you can easily create a 196 | local fork. 197 | -------------------------------------------------------------------------------- /basic_examples.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * this file demonstrates basic ggformat usage 3 | * 4 | * compile me with "cl.exe basic_examples.cpp ggformat.cpp" 5 | * or "g++ -std=c++11 basic_examples.cpp ggformat.cpp" 6 | */ 7 | 8 | #include 9 | #include "ggformat.h" 10 | 11 | struct v3 { 12 | explicit v3( float x_, float y_, float z_ ) { x = x_; y = y_; z = z_; } 13 | float x, y, z; 14 | }; 15 | 16 | v3 operator+( const v3 & lhs, const v3 & rhs ) { 17 | return v3( lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z ); 18 | } 19 | 20 | void format( FormatBuffer * fb, const v3 & v, const FormatOpts & opts ) { 21 | format( fb, "v3(" ); 22 | format( fb, v.x, opts ); 23 | format( fb, ", " ); 24 | format( fb, v.y, opts ); 25 | format( fb, ", " ); 26 | format( fb, v.z, opts ); 27 | format( fb, ")" ); 28 | } 29 | 30 | struct Thing { 31 | // pretend this is more complicated 32 | int a; 33 | float b; 34 | }; 35 | 36 | void format( FormatBuffer * fb, const Thing & thing, const FormatOpts & opts ) { 37 | // this is a bit of a hack but is occasionally useful 38 | // note that opts are ignored, rather than forwarded to a and b 39 | ggformat_impl( fb, "a = {}. b = {}", thing.a, thing.b ); 40 | } 41 | 42 | int main() { 43 | // basic types 44 | ggprint( "ints: {-5} {04} {+} {}\n", 1, 1, 1, 1 ); 45 | ggprint( "hex: 0x{04x}\n", 123 ); 46 | ggprint( "bin: 0b{b} 0b{b} 0b{b} 0b{b}\n", uint64_t( 123 ), int32_t( -123 ), uint16_t( 123 ), uint8_t( 123 ) ); 47 | ggprint( "floats: {-10} {4.2} {+} {}\n", 1.23, 1.23, 1.23, 1.23 ); 48 | ggprint( "bools: {} {}\n", true, false ); 49 | ggprint( "strings: {-10} {} {{ }}\n", "hello", "world" ); 50 | 51 | ggprint( "mins : {} {} {} {}\n", int64_t( INT64_MIN ), int32_t( INT32_MIN ), int16_t( INT16_MIN ), int8_t( INT8_MIN ) ); 52 | ggprint( "maxs : {} {} {} {}\n", int64_t( INT64_MAX ), int32_t( INT32_MAX ), int16_t( INT16_MAX ), int8_t( INT8_MAX ) ); 53 | ggprint( "umaxs: {} {} {} {}\n", uint64_t( UINT64_MAX ), uint32_t( UINT32_MAX ), uint16_t( UINT16_MAX ), uint8_t( UINT8_MAX ) ); 54 | 55 | ggprint( "mins : {x} {x} {x} {x}\n", int64_t( INT64_MIN ), int32_t( INT32_MIN ), int16_t( INT16_MIN ), int8_t( INT8_MIN ) ); 56 | ggprint( "maxs : {x} {x} {x} {x}\n", int64_t( INT64_MAX ), int32_t( INT32_MAX ), int16_t( INT16_MAX ), int8_t( INT8_MAX ) ); 57 | ggprint( "umaxs: {x} {x} {x} {x}\n", uint64_t( UINT64_MAX ), uint32_t( UINT32_MAX ), uint16_t( UINT16_MAX ), uint8_t( UINT8_MAX ) ); 58 | 59 | ggprint( "mins : {b} {b} {b} {b}\n", int64_t( INT64_MIN ), int32_t( INT32_MIN ), int16_t( INT16_MIN ), int8_t( INT8_MIN ) ); 60 | ggprint( "maxs : {b} {b} {b} {b}\n", int64_t( INT64_MAX ), int32_t( INT32_MAX ), int16_t( INT16_MAX ), int8_t( INT8_MAX ) ); 61 | ggprint( "umaxs: {b} {b} {b} {b}\n", uint64_t( UINT64_MAX ), uint32_t( UINT32_MAX ), uint16_t( UINT16_MAX ), uint8_t( UINT8_MAX ) ); 62 | 63 | // user defined type 64 | v3 a = v3( 1, 2, 3 ); 65 | v3 b = v3( 4, 5, 6 ); 66 | ggprint( "a = {}. b = {02.2}.\na + b = {+}\n", a, b, a + b ); 67 | 68 | // more complicated user defined type 69 | Thing thing; 70 | thing.a = 12345; 71 | thing.b = 67890; 72 | ggprint( "{}\n", thing ); 73 | 74 | return 0; 75 | } 76 | 77 | -------------------------------------------------------------------------------- /ggformat.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * ggformat v1.1 3 | * 4 | * Copyright (c) 2017 Michael Savage 5 | * 6 | * Permission to use, copy, modify, and distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "ggformat.h" 26 | 27 | static size_t ggformat_strlcat( char * dst, const char * src, size_t dsize ); 28 | static long long ggformat_strtonum( const char * numstr, long long minval, long long maxval, const char ** errstrp ); 29 | 30 | template< typename To, typename From > 31 | inline To checked_cast( const From & from ) { 32 | To result = To( from ); 33 | GGFORMAT_ASSERT( From( result ) == from ); 34 | return result; 35 | } 36 | 37 | namespace { 38 | struct ShortString { 39 | char buf[ 16 ]; 40 | 41 | ShortString() { 42 | buf[ 0 ] = '\0'; 43 | } 44 | 45 | void operator+=( int x ) { 46 | char num[ 16 ]; 47 | snprintf( num, sizeof( num ), "%d", x ); 48 | *this += num; 49 | } 50 | 51 | void operator+=( const char * str ) { 52 | ggformat_strlcat( buf, str, sizeof( buf ) ); 53 | } 54 | }; 55 | } 56 | 57 | template< typename T > 58 | static void format_helper( FormatBuffer * fb, const ::ShortString & fmt, const T & x ) { 59 | char * dst = fb->buf + fb->len; 60 | size_t len = fb->capacity - fb->len; 61 | 62 | if( fb->len >= fb->capacity ) { 63 | dst = NULL; 64 | len = 0; 65 | } 66 | 67 | #if GGFORMAT_COMPILER_MSVC 68 | #pragma warning( disable : 4996 ) // '_snprintf': This function or variable may be unsafe. 69 | int printed = _snprintf( NULL, 0, fmt.buf, x ); 70 | _snprintf( dst, len, fmt.buf, x ); 71 | #else 72 | int printed = snprintf( dst, len, fmt.buf, x ); 73 | #endif 74 | fb->len += checked_cast< size_t >( printed ); 75 | } 76 | 77 | void format( FormatBuffer * fb, double x, const FormatOpts & opts ) { 78 | ::ShortString fmt; 79 | fmt += "%"; 80 | int precision = opts.precision != -1 ? opts.precision : 5; 81 | if( opts.plus_sign ) fmt += "+"; 82 | if( opts.left_align ) fmt += "-"; 83 | if( opts.zero_pad ) fmt += "0"; 84 | if( opts.width != -1 ) fmt += opts.width + 1 + precision; 85 | fmt += "."; 86 | fmt += precision; 87 | fmt += "f"; 88 | format_helper( fb, fmt, x ); 89 | } 90 | 91 | void format( FormatBuffer * fb, char x, const FormatOpts & opts ) { 92 | ::ShortString fmt; 93 | fmt += "%"; 94 | if( opts.left_align ) fmt += "-"; 95 | if( opts.width != -1 ) fmt += opts.width; 96 | fmt += "c"; 97 | format_helper( fb, fmt, x ); 98 | } 99 | 100 | void format( FormatBuffer * fb, const char * x, const FormatOpts & opts ) { 101 | ::ShortString fmt; 102 | fmt += "%"; 103 | if( opts.left_align ) fmt += "-"; 104 | if( opts.width != -1 ) fmt += opts.width; 105 | fmt += "s"; 106 | format_helper( fb, fmt, x ); 107 | } 108 | 109 | void format( FormatBuffer * fb, bool x, const FormatOpts & opts ) { 110 | format( fb, x ? "true" : "false", opts ); 111 | } 112 | 113 | template< typename T > 114 | static void int_helper( FormatBuffer * fb, const char * fmt_length, const char * fmt_decimal, const T & x, const FormatOpts & opts ) { 115 | ::ShortString fmt; 116 | fmt += "%"; 117 | if( opts.plus_sign ) fmt += "+"; 118 | if( opts.left_align ) fmt += "-"; 119 | if( opts.zero_pad ) fmt += "0"; 120 | if( opts.width != -1 ) fmt += opts.width; 121 | if( opts.number_format == FormatOpts::DECIMAL ) { 122 | fmt += fmt_length; 123 | fmt += fmt_decimal; 124 | } 125 | else if( opts.number_format == FormatOpts::HEX ) { 126 | fmt += fmt_length; 127 | fmt += "x"; 128 | } 129 | else if( opts.number_format == FormatOpts::BINARY ) { 130 | fmt += "s"; 131 | char binary[ sizeof( x ) * 8 + 1 ]; 132 | binary[ sizeof( x ) * 8 ] = '\0'; 133 | 134 | for( size_t i = 0; i < sizeof( x ) * 8; i++ ) { 135 | unsigned long long bit = x & ( ( unsigned long long ) 1 << ( sizeof( x ) * 8 - i - 1 ) ); 136 | binary[ i ] = bit == 0 ? '0' : '1'; 137 | } 138 | 139 | format_helper( fb, fmt, binary ); 140 | return; 141 | } 142 | format_helper( fb, fmt, x ); 143 | } 144 | 145 | #define INT_OVERLOADS( T, fmt_length ) \ 146 | void format( FormatBuffer * fb, signed T x, const FormatOpts & opts ) { \ 147 | int_helper( fb, fmt_length, "d", x, opts ); \ 148 | } \ 149 | void format( FormatBuffer * fb, unsigned T x, const FormatOpts & opts ) { \ 150 | int_helper( fb, fmt_length, "u", x, opts ); \ 151 | } 152 | 153 | INT_OVERLOADS( char, "hh" ) 154 | INT_OVERLOADS( short, "h" ) 155 | INT_OVERLOADS( int, "" ) 156 | INT_OVERLOADS( long, "l" ) 157 | INT_OVERLOADS( long long, "ll" ) 158 | 159 | #undef INT_OVERLOADS 160 | 161 | static const char * parse_format_bool( const char * p, const char * one_past_end, char x, bool * out ) { 162 | if( p >= one_past_end ) return p; 163 | if( *p != x ) return p; 164 | *out = true; 165 | return p + 1; 166 | } 167 | 168 | static const char * parse_format_int( const char * p, const char * one_past_end, int * out ) { 169 | char num[ 16 ]; 170 | size_t num_len = 0; 171 | 172 | while( p + num_len < one_past_end && isdigit( p[ num_len ] ) ) { 173 | num[ num_len ] = p[ num_len ]; 174 | num_len++; 175 | } 176 | num[ num_len ] = '\0'; 177 | 178 | if( num_len == 0 ) return p; 179 | 180 | *out = int( ggformat_strtonum( num, 1, 1024, NULL ) ); 181 | GGFORMAT_ASSERT( *out != 0 ); 182 | 183 | return p + num_len; 184 | } 185 | 186 | static const char * parse_format_precision( const char * p, const char * one_past_end, int * precision ) { 187 | bool has_a_dot = false; 188 | const char * after_dot = parse_format_bool( p, one_past_end, '.', &has_a_dot ); 189 | if( !has_a_dot ) return p; 190 | return parse_format_int( after_dot, one_past_end, precision ); 191 | } 192 | 193 | static const char * parse_format_number_format( const char * p, const char * one_past_end, FormatOpts::NumberFormat * number_format ) { 194 | bool hex = false; 195 | const char * after_hex = parse_format_bool( p, one_past_end, 'x', &hex ); 196 | 197 | if( hex ) { 198 | *number_format = FormatOpts::HEX; 199 | return after_hex; 200 | } 201 | 202 | bool bin = false; 203 | const char * after_bin = parse_format_bool( p, one_past_end, 'b', &bin ); 204 | 205 | if( bin ) { 206 | *number_format = FormatOpts::BINARY; 207 | return after_bin; 208 | } 209 | 210 | return p; 211 | } 212 | 213 | FormatOpts parse_formatopts( const char * fmt, size_t len ) { 214 | FormatOpts opts; 215 | 216 | const char * start = fmt; 217 | const char * one_past_end = start + len; 218 | 219 | start = parse_format_bool( start, one_past_end, '+', &opts.plus_sign ); 220 | start = parse_format_bool( start, one_past_end, '-', &opts.left_align ); 221 | start = parse_format_bool( start, one_past_end, '0', &opts.zero_pad ); 222 | start = parse_format_int( start, one_past_end, &opts.width ); 223 | start = parse_format_precision( start, one_past_end, &opts.precision ); 224 | start = parse_format_number_format( start, one_past_end, &opts.number_format ); 225 | 226 | GGFORMAT_ASSERT( start == one_past_end ); 227 | 228 | return opts; 229 | } 230 | 231 | static bool strchridx( const char * haystack, char needle, size_t * idx, size_t skip = 0 ) { 232 | *idx = skip; 233 | while( haystack[ *idx ] != '\0' ) { 234 | if( haystack[ *idx ] == needle ) { 235 | return true; 236 | } 237 | ( *idx )++; 238 | } 239 | return false; 240 | } 241 | 242 | bool ggformat_find( const char * str, size_t * start, size_t * one_past_end ) { 243 | size_t open_idx; 244 | bool has_open = strchridx( str, '{', &open_idx ); 245 | if( has_open && str[ open_idx + 1 ] == '{' ) { 246 | has_open = false; 247 | } 248 | if( !has_open ) open_idx = 0; 249 | 250 | size_t close_idx; 251 | bool has_close = strchridx( str, '}', &close_idx, open_idx ); 252 | if( has_close && str[ close_idx + 1 ] == '}' ) { 253 | has_close = false; 254 | } 255 | 256 | if( has_open ) { 257 | GGFORMAT_ASSERT( has_close ); 258 | GGFORMAT_ASSERT( open_idx < close_idx ); 259 | 260 | *start = open_idx; 261 | *one_past_end = close_idx; 262 | 263 | return true; 264 | } 265 | 266 | GGFORMAT_ASSERT( !has_close ); 267 | return false; 268 | } 269 | 270 | void ggformat_literals( FormatBuffer * fb, const char * literals, size_t len ) { 271 | size_t copied_len = 0; 272 | for( size_t i = 0; i < len; i++ ) { 273 | if( literals[ i ] == '{' || literals[ i ] == '}' ) { 274 | i++; 275 | } 276 | if( fb->len + copied_len < fb->capacity ) { 277 | fb->buf[ fb->len + copied_len ] = literals[ i ]; 278 | } 279 | copied_len++; 280 | } 281 | fb->len += copied_len; 282 | if( fb->capacity > 0 ) { 283 | fb->buf[ fb->len < fb->capacity - 1 ? fb->len : fb->capacity - 1 ] = '\0'; 284 | } 285 | } 286 | 287 | void ggformat_impl( FormatBuffer * fb, const char * fmt ) { 288 | size_t ignored; 289 | GGFORMAT_ASSERT( !ggformat_find( fmt, &ignored, &ignored ) ); 290 | ggformat_literals( fb, fmt, strlen( fmt ) ); 291 | } 292 | 293 | /* 294 | * Copyright (c) 1998, 2015 Todd C. Miller 295 | * 296 | * Permission to use, copy, modify, and distribute this software for any 297 | * purpose with or without fee is hereby granted, provided that the above 298 | * copyright notice and this permission notice appear in all copies. 299 | * 300 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 301 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 302 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 303 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 304 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 305 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 306 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 307 | */ 308 | 309 | static size_t 310 | ggformat_strlcat(char *dst, const char *src, size_t dsize) 311 | { 312 | const char *odst = dst; 313 | const char *osrc = src; 314 | size_t n = dsize; 315 | size_t dlen; 316 | 317 | /* Find the end of dst and adjust bytes left but don't go past end. */ 318 | while (n-- != 0 && *dst != '\0') 319 | dst++; 320 | dlen = dst - odst; 321 | n = dsize - dlen; 322 | 323 | if (n-- == 0) 324 | return(dlen + strlen(src)); 325 | while (*src != '\0') { 326 | if (n != 0) { 327 | *dst++ = *src; 328 | n--; 329 | } 330 | src++; 331 | } 332 | *dst = '\0'; 333 | 334 | return(dlen + (src - osrc)); /* count does not include NUL */ 335 | } 336 | 337 | /* 338 | * Copyright (c) 2004 Ted Unangst and Todd Miller 339 | * All rights reserved. 340 | * 341 | * Permission to use, copy, modify, and distribute this software for any 342 | * purpose with or without fee is hereby granted, provided that the above 343 | * copyright notice and this permission notice appear in all copies. 344 | * 345 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 346 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 347 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 348 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 349 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 350 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 351 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 352 | */ 353 | 354 | #define INVALID 1 355 | #define TOOSMALL 2 356 | #define TOOLARGE 3 357 | 358 | static long long 359 | ggformat_strtonum(const char *numstr, long long minval, long long maxval, const char **errstrp) 360 | { 361 | long long ll = 0; 362 | char *ep; 363 | int error = 0; 364 | struct errval { 365 | const char *errstr; 366 | int err; 367 | } ev[4] = { 368 | { NULL, 0 }, 369 | { "invalid", EINVAL }, 370 | { "too small", ERANGE }, 371 | { "too large", ERANGE }, 372 | }; 373 | 374 | ev[0].err = errno; 375 | errno = 0; 376 | if (minval > maxval) 377 | error = INVALID; 378 | else { 379 | ll = strtoll(numstr, &ep, 10); 380 | if (numstr == ep || *ep != '\0') 381 | error = INVALID; 382 | else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval) 383 | error = TOOSMALL; 384 | else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval) 385 | error = TOOLARGE; 386 | } 387 | if (errstrp != NULL) 388 | *errstrp = ev[error].errstr; 389 | errno = ev[error].err; 390 | if (error) 391 | ll = 0; 392 | 393 | return (ll); 394 | } 395 | -------------------------------------------------------------------------------- /ggformat.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ggformat v1.1 3 | * 4 | * Copyright (c) 2017 Michael Savage 5 | * 6 | * Permission to use, copy, modify, and distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | #include 23 | 24 | /* 25 | * prototypes of the functions you should be calling 26 | */ 27 | 28 | /* 29 | * `ggformat` writes at most `len` bytes to `buf`, and that always includes a 30 | * null terminator. It returns the number of bytes that would have been written 31 | * if `buf` were large enough, not including the null terminator, and can be 32 | * larger than `len` (just like sprintf). 33 | */ 34 | template< typename... Rest > 35 | size_t ggformat( char * buf, size_t len, const char * fmt, const Rest & ... rest ); 36 | 37 | /* 38 | * `ggprint_to_file` does what you would expect, and `ggprint` writes to 39 | * standard output. Both return `true` on success, or `false` if the write 40 | * fails. 41 | */ 42 | template< typename... Rest > 43 | bool ggprint_to_file( FILE * file, const char * fmt, const Rest & ... rest ); 44 | 45 | template< typename... Rest > 46 | bool ggprint( const char * fmt, const Rest & ... rest ); 47 | 48 | /* 49 | * structures and functions used for formatting specific data types 50 | */ 51 | 52 | struct FormatOpts { 53 | enum NumberFormat { DECIMAL, HEX, BINARY }; 54 | 55 | int width = -1; 56 | int precision = -1; 57 | bool plus_sign = false; 58 | bool left_align = false; 59 | bool zero_pad = false; 60 | NumberFormat number_format = DECIMAL; 61 | }; 62 | 63 | struct FormatBuffer { 64 | FormatBuffer( char * b, size_t c ) { 65 | buf = b; 66 | capacity = c; 67 | len = 0; 68 | } 69 | 70 | char * buf; 71 | size_t capacity; 72 | size_t len; 73 | }; 74 | 75 | /* 76 | * format implementations for primitive types 77 | */ 78 | 79 | void format( FormatBuffer * fb, signed char x, const FormatOpts & opts ); 80 | void format( FormatBuffer * fb, short x, const FormatOpts & opts ); 81 | void format( FormatBuffer * fb, int x, const FormatOpts & opts ); 82 | void format( FormatBuffer * fb, long x, const FormatOpts & opts ); 83 | void format( FormatBuffer * fb, long long x, const FormatOpts & opts ); 84 | void format( FormatBuffer * fb, unsigned char x, const FormatOpts & opts ); 85 | void format( FormatBuffer * fb, unsigned short x, const FormatOpts & opts ); 86 | void format( FormatBuffer * fb, unsigned int x, const FormatOpts & opts ); 87 | void format( FormatBuffer * fb, unsigned long x, const FormatOpts & opts ); 88 | void format( FormatBuffer * fb, unsigned long long x, const FormatOpts & opts ); 89 | void format( FormatBuffer * fb, double x, const FormatOpts & opts ); 90 | void format( FormatBuffer * fb, bool x, const FormatOpts & opts ); 91 | void format( FormatBuffer * fb, char x, const FormatOpts & opts ); 92 | void format( FormatBuffer * fb, const char * x, const FormatOpts & opts = FormatOpts() ); 93 | 94 | /* 95 | * nasty implementation details that have to go in the header 96 | */ 97 | 98 | #define GGFORMAT_ASSERT( p ) \ 99 | do { \ 100 | if( !( p ) ) { \ 101 | fprintf( stderr, "assertion failed: %s\n", #p ); \ 102 | abort(); \ 103 | } \ 104 | } while( 0 ) 105 | 106 | #if defined( _MSC_VER ) 107 | # define GGFORMAT_COMPILER_MSVC 1 108 | #elif defined( __clang__ ) 109 | # define GGFORMAT_COMPILER_CLANG 1 110 | #elif defined( __GNUC__ ) 111 | # define GGFORMAT_COMPILER_GCC 1 112 | #else 113 | # error new compiler 114 | #endif 115 | 116 | // this is optional but helps compile times 117 | #if GGFORMAT_COMPILER_MSVC 118 | # define GGFORMAT_DISABLE_OPTIMISATIONS() __pragma( optimize( "", off ) ) 119 | # define GGFORMAT_ENABLE_OPTIMISATIONS() __pragma( optimize( "", on ) ) 120 | #elif GGFORMAT_COMPILER_GCC 121 | # define GGFORMAT_DISABLE_OPTIMISATIONS() \ 122 | _Pragma( "GCC push_options" ) \ 123 | _Pragma( "GCC optimize (\"O0\")" ) 124 | # define GGFORMAT_ENABLE_OPTIMISATIONS() _Pragma( "GCC pop_options" ) 125 | #elif GGFORMAT_COMPILER_CLANG 126 | # define GGFORMAT_DISABLE_OPTIMISATIONS() _Pragma( "clang optimize off" ) 127 | # define GGFORMAT_ENABLE_OPTIMISATIONS() _Pragma( "clang optimize on" ) 128 | #else 129 | # error new compiler 130 | #endif 131 | 132 | FormatOpts parse_formatopts( const char * fmt, size_t len ); 133 | void ggformat_impl( FormatBuffer * fb, const char * fmt ); 134 | bool ggformat_find( const char * str, size_t * start, size_t * one_past_end ); 135 | void ggformat_literals( FormatBuffer * fb, const char * literals, size_t len ); 136 | 137 | GGFORMAT_DISABLE_OPTIMISATIONS(); 138 | 139 | template< typename T, typename... Rest > 140 | void ggformat_impl( FormatBuffer * fb, const char * fmt, const T & first, const Rest & ... rest ) { 141 | size_t start, one_past_end; 142 | bool has_fmt = ggformat_find( fmt, &start, &one_past_end ); 143 | GGFORMAT_ASSERT( has_fmt ); 144 | 145 | ggformat_literals( fb, fmt, start ); 146 | 147 | FormatOpts opts = parse_formatopts( fmt + start + 1, one_past_end - start - 1 ); 148 | format( fb, first, opts ); 149 | 150 | ggformat_impl( fb, fmt + one_past_end + 1, rest... ); 151 | } 152 | 153 | template< typename... Rest > 154 | size_t ggformat( char * buf, size_t len, const char * fmt, const Rest & ... rest ) { 155 | FormatBuffer fb( buf, len ); 156 | ggformat_impl( &fb, fmt, rest... ); 157 | return fb.len; 158 | } 159 | 160 | template< typename... Rest > 161 | bool ggprint_to_file( FILE * file, const char * fmt, const Rest & ... rest ) { 162 | char buf[ 4096 ]; 163 | FormatBuffer fb( buf, sizeof( buf ) ); 164 | ggformat_impl( &fb, fmt, rest... ); 165 | 166 | if( fb.len < fb.capacity ) { 167 | size_t written = fwrite( buf, 1, fb.len, file ); 168 | return written == fb.len; 169 | } 170 | 171 | char * large_buf = ( char * ) malloc( fb.len + 1 ); 172 | GGFORMAT_ASSERT( large_buf != NULL ); 173 | FormatBuffer new_fb( large_buf, fb.len + 1 ); 174 | ggformat_impl( &new_fb, fmt, rest... ); 175 | size_t written = fwrite( large_buf, 1, fb.len, file ); 176 | free( large_buf ); 177 | 178 | return written == fb.len; 179 | } 180 | 181 | template< typename... Rest > 182 | bool ggprint( const char * fmt, const Rest & ... rest ) { 183 | return ggprint_to_file( stdout, fmt, rest... ); 184 | } 185 | 186 | GGFORMAT_ENABLE_OPTIMISATIONS(); 187 | -------------------------------------------------------------------------------- /string_examples.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * this file demonstrates integrating ggformat with a string class 3 | * 4 | * compile me with "cl.exe string_examples.cpp ggformat.cpp" 5 | * or "g++ -std=c++11 string_examples.cpp ggformat.cpp" 6 | */ 7 | 8 | #include "ggformat.h" 9 | 10 | template< typename T > 11 | T min( T a, T b ) { 12 | return a < b ? a : b; 13 | } 14 | 15 | template< size_t N > 16 | class str { 17 | public: 18 | str() { 19 | clear(); 20 | } 21 | 22 | template< typename... Rest > 23 | str( const char * fmt, const Rest & ... rest ) { 24 | sprintf( fmt, rest... ); 25 | } 26 | 27 | void clear() { 28 | buf[ 0 ] = '\0'; 29 | length = 0; 30 | } 31 | 32 | template< typename T > 33 | void operator+=( const T & x ) { 34 | appendf( "{}", x ); 35 | } 36 | 37 | template< typename... Rest > 38 | void sprintf( const char * fmt, const Rest & ... rest ) { 39 | size_t copied = ggformat( buf, N, fmt, rest... ); 40 | length = min( copied, N - 1 ); 41 | } 42 | 43 | template< typename... Rest > 44 | void appendf( const char * fmt, const Rest & ... rest ) { 45 | size_t copied = ggformat( buf + length, N - length, fmt, rest... ); 46 | length += min( copied, N - length - 1 ); 47 | } 48 | 49 | const char * c_str() const { 50 | return buf; 51 | } 52 | 53 | private: 54 | char buf[ N ]; 55 | size_t length; 56 | }; 57 | 58 | template< size_t N > 59 | void format( FormatBuffer * fb, const str< N > & buf, const FormatOpts & opts ) { 60 | format( fb, buf.c_str(), opts ); 61 | } 62 | 63 | int main() { 64 | str< 256 > a( "hello {-10}:", "world" ); 65 | a += " "; 66 | a += 1; 67 | a += " "; 68 | a += 1.2345; 69 | a += " "; 70 | a += false; 71 | a.appendf( ". {} w{}rld", "goodbye", 0 ); 72 | 73 | ggprint( "{}\n", a ); 74 | return 0; 75 | } 76 | --------------------------------------------------------------------------------