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 }