commit 2f6c2071ad38f3d81f52e7820323fd3aa08f1a80 parent 7d9fb5a0116e5baadbdf3ad667c685ec4fd1defd Author: Michael Savage <mikejsavage@gmail.com> Date: Sat Mar 4 11:02:28 +0200 Add ggformat Diffstat:
ggformat.cc | | | 194 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
ggformat.h | | | 126 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
diff --git a/ggformat.cc b/ggformat.cc @@ -0,0 +1,194 @@ +#include <ctype.h> + +#include "intrinsics.h" +#include "ggformat.h" +#include "str.h" // TODO: implement a subset of str +#include "strtonum.h" +#include "platform_io.h" + +template< typename T > +static void format_helper( FormatBuffer * fb, const str< 16 > & fmt, const T & x ) { + char * dst = fb->buf + fb->len; + size_t len = fb->capacity - fb->len; + + if( fb->len >= fb->capacity ) { + dst = NULL; + len = 0; + } + + int printed = snprintf( dst, len, fmt.c_str(), x ); + fb->len += checked_cast< size_t >( printed ); +} + +void format( FormatBuffer * fb, double x, const FormatOpts & opts ) { + str< 16 > fmt; + fmt += "%"; + if( opts.left_align ) fmt += "-"; + if( opts.width != -1 ) fmt += opts.width; + if( opts.precision != -1 ) { + fmt += "."; + fmt += opts.precision; + } + fmt += "f"; + format_helper( fb, fmt, x ); +} + +void format( FormatBuffer * fb, char x, const FormatOpts & opts ) { + str< 16 > fmt; + fmt += "%"; + if( opts.left_align ) fmt += "-"; + if( opts.width != -1 ) fmt += opts.width; + fmt += "c"; + format_helper( fb, fmt, x ); +} + +void format( FormatBuffer * fb, const char * x, const FormatOpts & opts ) { + str< 16 > fmt; + fmt += "%"; + if( opts.left_align ) fmt += "-"; + if( opts.width != -1 ) fmt += opts.width; + fmt += "s"; + format_helper( fb, fmt, x ); +} + +void format( FormatBuffer * fb, bool x, const FormatOpts & opts ) { + format( fb, x ? "true" : "false", opts ); +} + +template< typename T > +static void int_helper( FormatBuffer * fb, char fmt_decimal, const T & x, const FormatOpts & opts ) { + str< 16 > fmt; + fmt += "%"; + if( opts.left_align ) fmt += "-"; + if( opts.zero_pad ) fmt += "0"; + if( opts.width != -1 ) fmt += opts.width; + if( opts.number_format == FormatOpts::DECIMAL ) { + fmt += fmt_decimal; + } + else if( opts.number_format == FormatOpts::HEX ) { + fmt += "x"; + } + else if( opts.number_format == FormatOpts::BINARY ) { + ASSERT( !"not implemented" ); + } + format_helper( fb, fmt, x ); +} + +void format( FormatBuffer * fb, long long x, const FormatOpts & opts ) { + int_helper( fb, 'd', x, opts ); +} + +void format( FormatBuffer * fb, unsigned long long x, const FormatOpts & opts ) { + int_helper( fb, 'u', x, opts ); +} + +#define INT_OVERLOADS( T ) \ + void format( FormatBuffer * fb, T x, const FormatOpts & opts ) { \ + format( fb, ( long long ) x, opts ); \ + } \ + void format( FormatBuffer * fb, unsigned T x, const FormatOpts & opts ) { \ + format( fb, ( unsigned long long ) x, opts ); \ + } + +INT_OVERLOADS( short ) +INT_OVERLOADS( int ) +INT_OVERLOADS( long ) + +#undef INT_OVERLOADS + +static const char * parse_format_bool( const char * p, const char * one_past_end, char x, bool * out ) { + if( p >= one_past_end ) return p; + if( *p != x ) return p; + *out = true; + return p + 1; +} + +static const char * parse_format_int( const char * p, const char * one_past_end, int * out ) { + char num[ 16 ]; + size_t num_len = 0; + + while( p + num_len < one_past_end && isdigit( p[ num_len ] ) ) { + num[ num_len ] = p[ num_len ]; + num_len++; + } + num[ num_len ] = '\0'; + + if( num_len == 0 ) return p; + + *out = strtonum( num, 1, 1024, NULL ); + ASSERT( *out != 0 ); + + return p + num_len; +} + +static const char * parse_format_precision( const char * p, const char * one_past_end, int * precision ) { + bool has_a_dot = false; + const char * after_dot = parse_format_bool( p, one_past_end, '.', &has_a_dot ); + if( !has_a_dot ) return p; + return parse_format_int( after_dot, one_past_end, precision ); +} + +static const char * parse_format_number_format( const char * p, const char * one_past_end, FormatOpts::NumberFormat * number_format ) { + *number_format = FormatOpts::DECIMAL; + + bool hex = false; + const char * after_hex = parse_format_bool( p, one_past_end, 'x', &hex ); + + if( hex ) { + *number_format = FormatOpts::HEX; + return after_hex; + } + + bool bin = false; + const char * after_bin = parse_format_bool( p, one_past_end, 'b', &bin ); + + if( bin ) { + *number_format = FormatOpts::BINARY; + return after_bin; + } + + return p; +} + + +FormatOpts parse_formatopts( const char * fmt, size_t len ) { + FormatOpts opts; + + const char * start = fmt; + const char * one_past_end = start + len; + + start = parse_format_bool( start, one_past_end, '-', &opts.left_align ); + start = parse_format_bool( start, one_past_end, '0', &opts.zero_pad ); + start = parse_format_int( start, one_past_end, &opts.width ); + start = parse_format_precision( start, one_past_end, &opts.precision ); + start = parse_format_number_format( start, one_past_end, &opts.number_format ); + + ASSERT( start == one_past_end ); + + return opts; +} + +static bool strchridx( const char * haystack, char needle, size_t * idx, size_t skip = 0 ) { + *idx = skip; + while( haystack[ *idx ] != '\0' ) { + if( haystack[ *idx ] == needle ) { + return true; + } + ( *idx )++; + } + return false; +} + +bool ggformat_find( const char * str, size_t * start, size_t * one_past_end ) { + bool has_open = strchridx( str, '{', start ); + if( !has_open ) return false; + bool has_close = strchridx( str, '}', one_past_end, *start ); + ASSERT( has_close ); + return true; +} + +void ggformat_impl( FormatBuffer * fb, const char * fmt ) { + size_t ignored; + ASSERT( !ggformat_find( fmt, &ignored, &ignored ) ); + format( fb, fmt ); +} diff --git a/ggformat.h b/ggformat.h @@ -0,0 +1,126 @@ +#pragma once + +#include <stddef.h> + +/* + * a type safe replacement for printf + */ + +/* + * prototypes of the functions you should be calling + */ +template< typename... Rest > +size_t ggformat( char * buf, size_t len, const char * fmt, Rest... rest ); + +template< typename... Rest > +void ggprint_to_file( FILE * file, const char * fmt, Rest... rest ); + +template< typename... Rest > +void ggprint( const char * fmt, Rest... rest ); + +/* + * structures and functions used for formatting specific data types + */ + +struct FormatOpts { + enum NumberFormat { DECIMAL, HEX, BINARY }; + + int width = -1; + int precision = -1; + bool zero_pad = false; + bool left_align = false; + NumberFormat number_format = DECIMAL; +}; + +struct FormatBuffer { + FormatBuffer( char * b, size_t c ) { + buf = b; + capacity = c; + len = 0; + } + + char * buf; + size_t capacity; + size_t len; +}; + +/* + * format implementations for primitive types + */ + +void format( FormatBuffer * fb, short x, const FormatOpts & opts ); +void format( FormatBuffer * fb, int x, const FormatOpts & opts ); +void format( FormatBuffer * fb, long x, const FormatOpts & opts ); +void format( FormatBuffer * fb, long long x, const FormatOpts & opts ); +void format( FormatBuffer * fb, unsigned short x, const FormatOpts & opts ); +void format( FormatBuffer * fb, unsigned int x, const FormatOpts & opts ); +void format( FormatBuffer * fb, unsigned long x, const FormatOpts & opts ); +void format( FormatBuffer * fb, unsigned long long x, const FormatOpts & opts ); +void format( FormatBuffer * fb, double x, const FormatOpts & opts ); +void format( FormatBuffer * fb, bool x, const FormatOpts & opts ); +void format( FormatBuffer * fb, char x, const FormatOpts & opts ); +void format( FormatBuffer * fb, const char * x, const FormatOpts & opts = FormatOpts() ); + +/* + * nasty implementation details that have to go in the header + */ + +#include "platform_io.h" +#include "platform_pragmas.h" + +FormatOpts parse_formatopts( const char * fmt, size_t len ); +void ggformat_impl( FormatBuffer * fb, const char * fmt ); +bool ggformat_find( const char * str, size_t * start, size_t * one_past_end ); + +PRAGMA_DISABLE_OPTIMISATIONS(); + +template< typename T, typename... Rest > +void ggformat_impl( FormatBuffer * fb, const char * fmt, const T & first, Rest... rest ) { + size_t start, one_past_end; + bool has_fmt = ggformat_find( fmt, &start, &one_past_end ); + ASSERT( has_fmt ); + + if( fb->len < fb->capacity ) { + size_t remaining = fb->capacity - fb->len; + size_t to_copy = min( remaining, start + 1 ); + snprintf( fb->buf + fb->len, to_copy, "%s", fmt ); + } + fb->len += start; + + FormatOpts opts = parse_formatopts( fmt + start + 1, one_past_end - start - 1 ); + format( fb, first, opts ); + + ggformat_impl( fb, fmt + one_past_end + 1, rest... ); +} + +template< typename... Rest > +size_t ggformat( char * buf, size_t len, const char * fmt, Rest... rest ) { + FormatBuffer fb( buf, sizeof( len ) ); + ggformat_impl( &fb, fmt, rest... ); + return fb.len; +} + +template< typename... Rest > +void ggprint_to_file( FILE * file, const char * fmt, Rest... rest ) { + char buf[ 4096 ]; + FormatBuffer fb( buf, sizeof( buf ) ); + ggformat_impl( &fb, fmt, rest... ); + + if( fb.len < fb.capacity ) { + fprintf( file, "%s", buf ); + return; + } + + char * new_buf = new char[ fb.len + 1 ](); + FormatBuffer new_fb( new_buf, fb.len + 1 ); + ggformat_impl( &new_fb, fmt, rest... ); + fprintf( file, "%s", new_buf ); + delete[] new_buf; +} + +template< typename... Rest > +void ggprint( const char * fmt, Rest... rest ) { + ggprint_to_file( stdout, fmt, rest... ); +} + +PRAGMA_ENABLE_OPTIMISATIONS();