ggformat

A string formatting library for C++
Log | Files | Refs | README

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 }