ggformat.cc (11066B)
1 /* 2 * ggformat v1.0 3 * 4 * Copyright (c) 2017 Michael Savage <mike@mikejsavage.co.uk> 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 <stdlib.h> 20 #include <string.h> 21 #include <errno.h> 22 #include <limits.h> 23 #include <ctype.h> 24 25 #include "ggformat.h" 26 27 static size_t strlcat( char * dst, const char * src, size_t dsize ); 28 static long long 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 struct ShortString { 38 char buf[ 16 ]; 39 40 ShortString() { 41 buf[ 0 ] = '\0'; 42 } 43 44 void operator+=( int x ) { 45 char num[ 16 ]; 46 snprintf( num, sizeof( num ), "%d", x ); 47 *this += num; 48 } 49 50 void operator+=( const char * str ) { 51 strlcat( buf, str, sizeof( buf ) ); 52 } 53 }; 54 55 template< typename T > 56 static void format_helper( FormatBuffer * fb, const ShortString & fmt, const T & x ) { 57 char * dst = fb->buf + fb->len; 58 size_t len = fb->capacity - fb->len; 59 60 if( fb->len >= fb->capacity ) { 61 dst = NULL; 62 len = 0; 63 } 64 65 #if GGFORMAT_COMPILER_MSVC 66 #pragma warning( disable : 4996 ) // '_snprintf': This function or variable may be unsafe. 67 int printed = _snprintf( NULL, 0, fmt.buf, x ); 68 _snprintf( dst, len, fmt.buf, x ); 69 #else 70 int printed = snprintf( dst, len, fmt.buf, x ); 71 #endif 72 fb->len += checked_cast< size_t >( printed ); 73 } 74 75 void format( FormatBuffer * fb, double x, const FormatOpts & opts ) { 76 ShortString fmt; 77 fmt += "%"; 78 int precision = opts.precision != -1 ? opts.precision : 5; 79 if( opts.plus_sign ) fmt += "+"; 80 if( opts.left_align ) fmt += "-"; 81 if( opts.zero_pad ) fmt += "0"; 82 if( opts.width != -1 ) fmt += opts.width + 1 + precision; 83 fmt += "."; 84 fmt += precision; 85 fmt += "f"; 86 format_helper( fb, fmt, x ); 87 } 88 89 void format( FormatBuffer * fb, char x, const FormatOpts & opts ) { 90 ShortString fmt; 91 fmt += "%"; 92 if( opts.left_align ) fmt += "-"; 93 if( opts.width != -1 ) fmt += opts.width; 94 fmt += "c"; 95 format_helper( fb, fmt, x ); 96 } 97 98 void format( FormatBuffer * fb, const char * x, const FormatOpts & opts ) { 99 ShortString fmt; 100 fmt += "%"; 101 if( opts.left_align ) fmt += "-"; 102 if( opts.width != -1 ) fmt += opts.width; 103 fmt += "s"; 104 format_helper( fb, fmt, x ); 105 } 106 107 void format( FormatBuffer * fb, bool x, const FormatOpts & opts ) { 108 format( fb, x ? "true" : "false", opts ); 109 } 110 111 template< typename T > 112 static void int_helper( FormatBuffer * fb, const char * fmt_length, const char * fmt_decimal, const T & x, const FormatOpts & opts ) { 113 ShortString fmt; 114 fmt += "%"; 115 if( opts.plus_sign ) fmt += "+"; 116 if( opts.left_align ) fmt += "-"; 117 if( opts.zero_pad ) fmt += "0"; 118 if( opts.width != -1 ) fmt += opts.width; 119 if( opts.number_format == FormatOpts::DECIMAL ) { 120 fmt += fmt_length; 121 fmt += fmt_decimal; 122 } 123 else if( opts.number_format == FormatOpts::HEX ) { 124 fmt += fmt_length; 125 fmt += "x"; 126 } 127 else if( opts.number_format == FormatOpts::BINARY ) { 128 fmt += "s"; 129 char binary[ sizeof( x ) * 8 + 1 ]; 130 binary[ sizeof( x ) * 8 ] = '\0'; 131 132 for( size_t i = 0; i < sizeof( x ) * 8; i++ ) { 133 // this is UB for signed types, but who cares? 134 T bit = x & ( T( 1 ) << ( sizeof( x ) * 8 - i - 1 ) ); 135 binary[ i ] = bit == 0 ? '0' : '1'; 136 } 137 138 format_helper( fb, fmt, binary ); 139 return; 140 } 141 format_helper( fb, fmt, x ); 142 } 143 144 #define INT_OVERLOADS( T, fmt_length ) \ 145 void format( FormatBuffer * fb, signed T x, const FormatOpts & opts ) { \ 146 int_helper( fb, fmt_length, "d", x, opts ); \ 147 } \ 148 void format( FormatBuffer * fb, unsigned T x, const FormatOpts & opts ) { \ 149 int_helper( fb, fmt_length, "u", x, opts ); \ 150 } 151 152 INT_OVERLOADS( char, "hh" ) 153 INT_OVERLOADS( short, "h" ) 154 INT_OVERLOADS( int, "" ) 155 INT_OVERLOADS( long, "l" ) 156 INT_OVERLOADS( long long, "ll" ) 157 158 #undef INT_OVERLOADS 159 160 static const char * parse_format_bool( const char * p, const char * one_past_end, char x, bool * out ) { 161 if( p >= one_past_end ) return p; 162 if( *p != x ) return p; 163 *out = true; 164 return p + 1; 165 } 166 167 static const char * parse_format_int( const char * p, const char * one_past_end, int * out ) { 168 char num[ 16 ]; 169 size_t num_len = 0; 170 171 while( p + num_len < one_past_end && isdigit( p[ num_len ] ) ) { 172 num[ num_len ] = p[ num_len ]; 173 num_len++; 174 } 175 num[ num_len ] = '\0'; 176 177 if( num_len == 0 ) return p; 178 179 *out = int( strtonum( num, 1, 1024, NULL ) ); 180 GGFORMAT_ASSERT( *out != 0 ); 181 182 return p + num_len; 183 } 184 185 static const char * parse_format_precision( const char * p, const char * one_past_end, int * precision ) { 186 bool has_a_dot = false; 187 const char * after_dot = parse_format_bool( p, one_past_end, '.', &has_a_dot ); 188 if( !has_a_dot ) return p; 189 return parse_format_int( after_dot, one_past_end, precision ); 190 } 191 192 static const char * parse_format_number_format( const char * p, const char * one_past_end, FormatOpts::NumberFormat * number_format ) { 193 bool hex = false; 194 const char * after_hex = parse_format_bool( p, one_past_end, 'x', &hex ); 195 196 if( hex ) { 197 *number_format = FormatOpts::HEX; 198 return after_hex; 199 } 200 201 bool bin = false; 202 const char * after_bin = parse_format_bool( p, one_past_end, 'b', &bin ); 203 204 if( bin ) { 205 *number_format = FormatOpts::BINARY; 206 return after_bin; 207 } 208 209 return p; 210 } 211 212 FormatOpts parse_formatopts( const char * fmt, size_t len ) { 213 FormatOpts opts; 214 215 const char * start = fmt; 216 const char * one_past_end = start + len; 217 218 start = parse_format_bool( start, one_past_end, '+', &opts.plus_sign ); 219 start = parse_format_bool( start, one_past_end, '-', &opts.left_align ); 220 start = parse_format_bool( start, one_past_end, '0', &opts.zero_pad ); 221 start = parse_format_int( start, one_past_end, &opts.width ); 222 start = parse_format_precision( start, one_past_end, &opts.precision ); 223 start = parse_format_number_format( start, one_past_end, &opts.number_format ); 224 225 GGFORMAT_ASSERT( start == one_past_end ); 226 227 return opts; 228 } 229 230 static bool strchridx( const char * haystack, char needle, size_t * idx, size_t skip = 0 ) { 231 *idx = skip; 232 while( haystack[ *idx ] != '\0' ) { 233 if( haystack[ *idx ] == needle ) { 234 return true; 235 } 236 ( *idx )++; 237 } 238 return false; 239 } 240 241 bool ggformat_find( const char * str, size_t * start, size_t * one_past_end ) { 242 size_t open_idx; 243 bool has_open = strchridx( str, '{', &open_idx ); 244 if( has_open && str[ open_idx + 1 ] == '{' ) { 245 has_open = false; 246 } 247 if( !has_open ) open_idx = 0; 248 249 size_t close_idx; 250 bool has_close = strchridx( str, '}', &close_idx, open_idx ); 251 if( has_close && str[ close_idx + 1 ] == '}' ) { 252 has_close = false; 253 } 254 255 if( has_open ) { 256 GGFORMAT_ASSERT( has_close ); 257 GGFORMAT_ASSERT( open_idx < close_idx ); 258 259 *start = open_idx; 260 *one_past_end = close_idx; 261 262 return true; 263 } 264 265 GGFORMAT_ASSERT( !has_close ); 266 return false; 267 } 268 269 void ggformat_literals( FormatBuffer * fb, const char * literals, size_t len ) { 270 size_t copied_len = 0; 271 for( size_t i = 0; i < len; i++ ) { 272 if( literals[ i ] == '{' || literals[ i ] == '}' ) { 273 i++; 274 } 275 if( fb->len + copied_len < fb->capacity ) { 276 fb->buf[ fb->len + copied_len ] = literals[ i ]; 277 } 278 copied_len++; 279 } 280 fb->len += copied_len; 281 if( fb->capacity > 0 ) { 282 fb->buf[ fb->len < fb->capacity - 1 ? fb->len : fb->capacity - 1 ] = '\0'; 283 } 284 } 285 286 void ggformat_impl( FormatBuffer * fb, const char * fmt ) { 287 size_t ignored; 288 GGFORMAT_ASSERT( !ggformat_find( fmt, &ignored, &ignored ) ); 289 ggformat_literals( fb, fmt, strlen( fmt ) ); 290 } 291 292 /* 293 * Copyright (c) 1998, 2015 Todd C. Miller <Todd.Miller@courtesan.com> 294 * 295 * Permission to use, copy, modify, and distribute this software for any 296 * purpose with or without fee is hereby granted, provided that the above 297 * copyright notice and this permission notice appear in all copies. 298 * 299 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 300 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 301 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 302 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 303 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 304 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 305 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 306 */ 307 308 static size_t 309 strlcat(char *dst, const char *src, size_t dsize) 310 { 311 const char *odst = dst; 312 const char *osrc = src; 313 size_t n = dsize; 314 size_t dlen; 315 316 /* Find the end of dst and adjust bytes left but don't go past end. */ 317 while (n-- != 0 && *dst != '\0') 318 dst++; 319 dlen = dst - odst; 320 n = dsize - dlen; 321 322 if (n-- == 0) 323 return(dlen + strlen(src)); 324 while (*src != '\0') { 325 if (n != 0) { 326 *dst++ = *src; 327 n--; 328 } 329 src++; 330 } 331 *dst = '\0'; 332 333 return(dlen + (src - osrc)); /* count does not include NUL */ 334 } 335 336 /* 337 * Copyright (c) 2004 Ted Unangst and Todd Miller 338 * All rights reserved. 339 * 340 * Permission to use, copy, modify, and distribute this software for any 341 * purpose with or without fee is hereby granted, provided that the above 342 * copyright notice and this permission notice appear in all copies. 343 * 344 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 345 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 346 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 347 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 348 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 349 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 350 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 351 */ 352 353 #define INVALID 1 354 #define TOOSMALL 2 355 #define TOOLARGE 3 356 357 static long long 358 strtonum(const char *numstr, long long minval, long long maxval, const char **errstrp) 359 { 360 long long ll = 0; 361 char *ep; 362 int error = 0; 363 struct errval { 364 const char *errstr; 365 int err; 366 } ev[4] = { 367 { NULL, 0 }, 368 { "invalid", EINVAL }, 369 { "too small", ERANGE }, 370 { "too large", ERANGE }, 371 }; 372 373 ev[0].err = errno; 374 errno = 0; 375 if (minval > maxval) 376 error = INVALID; 377 else { 378 ll = strtoll(numstr, &ep, 10); 379 if (numstr == ep || *ep != '\0') 380 error = INVALID; 381 else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval) 382 error = TOOSMALL; 383 else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval) 384 error = TOOLARGE; 385 } 386 if (errstrp != NULL) 387 *errstrp = ev[error].errstr; 388 errno = ev[error].err; 389 if (error) 390 ll = 0; 391 392 return (ll); 393 }