medfall

A super great game engine
Log | Files | Refs

ggformat.cc (6756B)


      1 #include <ctype.h>
      2 
      3 #include "intrinsics.h"
      4 #include "ggformat.h"
      5 #include "strtonum.h"
      6 #include "strlcat.h"
      7 #include "platform_io.h"
      8 
      9 struct ShortString {
     10 	char buf[ 16 ];
     11 
     12 	ShortString() {
     13 		buf[ 0 ] = '\0';
     14 	}
     15 
     16 	void operator+=( int x ) {
     17 		char num[ 16 ];
     18 		snprintf( num, sizeof( num ), "%d", x );
     19 		*this += num;
     20 	}
     21 
     22 	void operator+=( const char * str ) {
     23 		strlcat( buf, str, sizeof( buf ) );
     24 	}
     25 };
     26 
     27 template< typename T >
     28 static void format_helper( FormatBuffer * fb, const ShortString & fmt, const T & x ) {
     29 	char * dst = fb->buf + fb->len;
     30 	size_t len = fb->capacity - fb->len;
     31 
     32 	if( fb->len >= fb->capacity ) {
     33 		dst = NULL;
     34 		len = 0;
     35 	}
     36 
     37 #if PLATFORM_WINDOWS
     38 	int printed = _snprintf( NULL, 0, fmt.buf, x );
     39 	_snprintf( dst, len, fmt.buf, x );
     40 #else
     41 	int printed = snprintf( dst, len, fmt.buf, x );
     42 #endif
     43 	fb->len += checked_cast< size_t >( printed );
     44 }
     45 
     46 void format( FormatBuffer * fb, double x, const FormatOpts & opts ) {
     47 	ShortString fmt;
     48 	fmt += "%";
     49 	int precision = opts.precision != -1 ? opts.precision : 5;
     50 	if( opts.plus_sign ) fmt += "+";
     51 	if( opts.left_align ) fmt += "-";
     52 	if( opts.zero_pad ) fmt += "0";
     53 	if( opts.width != -1 ) fmt += opts.width + 1 + precision;
     54 	fmt += ".";
     55 	fmt += precision;
     56 	fmt += "f";
     57 	format_helper( fb, fmt, x );
     58 }
     59 
     60 void format( FormatBuffer * fb, char x, const FormatOpts & opts ) {
     61 	ShortString fmt;
     62 	fmt += "%";
     63 	if( opts.left_align ) fmt += "-";
     64 	if( opts.width != -1 ) fmt += opts.width;
     65 	fmt += "c";
     66 	format_helper( fb, fmt, x );
     67 }
     68 
     69 void format( FormatBuffer * fb, const char * x, const FormatOpts & opts ) {
     70 	ShortString fmt;
     71 	fmt += "%";
     72 	if( opts.left_align ) fmt += "-";
     73 	if( opts.width != -1 ) fmt += opts.width;
     74 	fmt += "s";
     75 	format_helper( fb, fmt, x );
     76 }
     77 
     78 void format( FormatBuffer * fb, bool x, const FormatOpts & opts ) {
     79 	format( fb, x ? "true" : "false", opts );
     80 }
     81 
     82 template< typename T >
     83 static void int_helper( FormatBuffer * fb, const char * fmt_length, const char * fmt_decimal, const T & x, const FormatOpts & opts ) {
     84 	ShortString fmt;
     85 	fmt += "%";
     86 	if( opts.plus_sign ) fmt += "+";
     87 	if( opts.left_align ) fmt += "-";
     88 	if( opts.zero_pad ) fmt += "0";
     89 	if( opts.width != -1 ) fmt += opts.width;
     90 	if( opts.number_format == FormatOpts::DECIMAL ) {
     91 		fmt += fmt_length;
     92 		fmt += fmt_decimal;
     93 	}
     94 	else if( opts.number_format == FormatOpts::HEX ) {
     95 		fmt += fmt_length;
     96 		fmt += "x";
     97 	}
     98 	else if( opts.number_format == FormatOpts::BINARY ) {
     99 		fmt += "s";
    100 		char binary[ sizeof( x ) * 8 + 1 ];
    101 		binary[ sizeof( x ) * 8 ] = '\0';
    102 
    103 		for( size_t i = 0; i < sizeof( x ) * 8; i++ ) {
    104 			// this is UB for signed types, but who cares?
    105 			T bit = x & ( T( 1 ) << ( sizeof( x ) * 8 - i - 1 ) );
    106 			binary[ i ] = bit == 0 ? '0' : '1';
    107 		}
    108 
    109 		format_helper( fb, fmt, binary );
    110 		return;
    111 	}
    112 	format_helper( fb, fmt, x );
    113 }
    114 
    115 #define INT_OVERLOADS( T, fmt_length ) \
    116 	void format( FormatBuffer * fb, signed T x, const FormatOpts & opts ) { \
    117 		int_helper( fb, fmt_length, "d", x, opts ); \
    118 	} \
    119 	void format( FormatBuffer * fb, unsigned T x, const FormatOpts & opts ) { \
    120 		int_helper( fb, fmt_length, "u", x, opts ); \
    121 	}
    122 
    123 INT_OVERLOADS( char, "hh" )
    124 INT_OVERLOADS( short, "h" )
    125 INT_OVERLOADS( int, "" )
    126 INT_OVERLOADS( long, "l" )
    127 INT_OVERLOADS( long long, "ll" )
    128 
    129 #undef INT_OVERLOADS
    130 
    131 static const char * parse_format_bool( const char * p, const char * one_past_end, char x, bool * out ) {
    132 	if( p >= one_past_end ) return p;
    133 	if( *p != x ) return p;
    134 	*out = true;
    135 	return p + 1;
    136 }
    137 
    138 static const char * parse_format_int( const char * p, const char * one_past_end, int * out ) {
    139 	char num[ 16 ];
    140 	size_t num_len = 0;
    141 
    142 	while( p + num_len < one_past_end && isdigit( p[ num_len ] ) ) {
    143 		num[ num_len ] = p[ num_len ];
    144 		num_len++;
    145 	}
    146 	num[ num_len ] = '\0';
    147 
    148 	if( num_len == 0 ) return p;
    149 
    150 	*out = int( strtonum( num, 1, 1024, NULL ) );
    151 	ASSERT( *out != 0 );
    152 
    153 	return p + num_len;
    154 }
    155 
    156 static const char * parse_format_precision( const char * p, const char * one_past_end, int * precision ) {
    157 	bool has_a_dot = false;
    158 	const char * after_dot = parse_format_bool( p, one_past_end, '.', &has_a_dot );
    159 	if( !has_a_dot ) return p;
    160 	return parse_format_int( after_dot, one_past_end, precision );
    161 }
    162 
    163 static const char * parse_format_number_format( const char * p, const char * one_past_end, FormatOpts::NumberFormat * number_format ) {
    164 	bool hex = false;
    165 	const char * after_hex = parse_format_bool( p, one_past_end, 'x', &hex );
    166 
    167 	if( hex ) {
    168 		*number_format = FormatOpts::HEX;
    169 		return after_hex;
    170 	}
    171 
    172 	bool bin = false;
    173 	const char * after_bin = parse_format_bool( p, one_past_end, 'b', &bin );
    174 
    175 	if( bin ) {
    176 		*number_format = FormatOpts::BINARY;
    177 		return after_bin;
    178 	}
    179 
    180 	return p;
    181 }
    182 
    183 
    184 FormatOpts parse_formatopts( const char * fmt, size_t len ) {
    185 	FormatOpts opts;
    186 
    187 	const char * start = fmt;
    188 	const char * one_past_end = start + len;
    189 
    190 	start = parse_format_bool( start, one_past_end, '+', &opts.plus_sign );
    191 	start = parse_format_bool( start, one_past_end, '-', &opts.left_align );
    192 	start = parse_format_bool( start, one_past_end, '0', &opts.zero_pad );
    193 	start = parse_format_int( start, one_past_end, &opts.width );
    194 	start = parse_format_precision( start, one_past_end, &opts.precision );
    195 	start = parse_format_number_format( start, one_past_end, &opts.number_format );
    196 
    197 	ASSERT( start == one_past_end );
    198 
    199 	return opts;
    200 }
    201 
    202 static bool strchridx( const char * haystack, char needle, size_t * idx, size_t skip = 0 ) {
    203 	*idx = skip;
    204 	while( haystack[ *idx ] != '\0' ) {
    205 		if( haystack[ *idx ] == needle ) {
    206 			return true;
    207 		}
    208 		( *idx )++;
    209 	}
    210 	return false;
    211 }
    212 
    213 bool ggformat_find( const char * str, size_t * start, size_t * one_past_end ) {
    214 	size_t open_idx;
    215 	bool has_open = strchridx( str, '{', &open_idx );
    216 	if( has_open && str[ open_idx + 1 ] == '{' ) {
    217 		has_open = false;
    218 	}
    219 	if( !has_open ) open_idx = 0;
    220 
    221 	size_t close_idx;
    222 	bool has_close = strchridx( str, '}', &close_idx, open_idx );
    223 	if( has_close && str[ close_idx + 1 ] == '}' ) {
    224 		has_close = false;
    225 	}
    226 
    227 	if( has_open ) {
    228 		ASSERT( has_close );
    229 		ASSERT( open_idx < close_idx );
    230 
    231 		*start = open_idx;
    232 		*one_past_end = close_idx;
    233 
    234 		return true;
    235 	}
    236 
    237 	ASSERT( !has_close );
    238 	return false;
    239 }
    240 
    241 void ggformat_literals( FormatBuffer * fb, const char * literals, size_t len ) {
    242 	size_t copied_len = 0;
    243 	for( size_t i = 0; i < len; i++ ) {
    244 		if( literals[ i ] == '{' || literals[ i ] == '}' ) {
    245 			i++;
    246 		}
    247 		if( fb->len + copied_len < fb->capacity ) {
    248 			fb->buf[ fb->len + copied_len ] = literals[ i ];
    249 		}
    250 		copied_len++;
    251 	}
    252 	fb->len += copied_len;
    253 	if( fb->capacity > 0 ) {
    254 		fb->buf[ fb->len < fb->capacity - 1 ? fb->len : fb->capacity - 1 ] = '\0';
    255 	}
    256 }
    257 
    258 void ggformat_impl( FormatBuffer * fb, const char * fmt ) {
    259 	size_t ignored;
    260 	ASSERT( !ggformat_find( fmt, &ignored, &ignored ) );
    261 	ggformat_literals( fb, fmt, strlen( fmt ) );
    262 }