make your code base a home base

BYOprintf

writing stuff from scratch is delightfully infective

i started using Vjekoslav Krajačić-style sliceable strings a while back: STRUCT { U8 *_; // NOTE you could also name this `data`, `v`, etc. U64 length; } String; they're great. you can slice in O(1) and you don't need to null-terminate // NOTE analogous to Python's string[start:one_past_end] String _string_slice(String string, U32 start, U32 one_past_end) { ASSERT(start <= string.length); ASSERT(start <= one_past_end); // NOTE we allow empty slices ASSERT(one_past_end <= string.length); String result; result._ = &string._[start]; result.length = (one_past_end - start); return(result); } // NOTE analogous to Python's string[start:] String string_slice(String string, U32 start) { return _string_slice(string, start, string.length); } the only downside i've experienced with using these String's is that the standard printf doesn't know about them -- pass an unterminated String as %s, and printf will just march merrily off into the distance, looking for a 0 that may never come i saw two ways forward (like always, there are probably more options than just these two): (a) remember to add null-terminators whenever i wanted to print (or sprint), or... (b) bring my own printf conventional wisdom has that option (b) is Bad and Hard, but conventional wisdom is basically always wrong, so let's go with option (b) :) like many Hard things, it ended up taking about 5 minutes: (1) copy and paste Marco Paland's tiny printf from GitHub1, and (2) add ~20 lines to it: case 'S' : { // our custom String struct String string = va_arg(va, String); const char *p = string._; unsigned int l = (unsigned int) string.length; if (l) { // NOTE this block (mostly) copy and pasted from case 's' // (be sure to change / add one tiny thing) ... } } that's it. and now we can do stuff like this... printf("[gui_tracked_box] box->key_string clash: %S\n", box->key_string); and since snprintf and vsnprintf also know about our String's now, we can use %S format specifiers in other functions, like... GUIBox *box_field_buffer = gui_tracked_box( "buffer_%S%S%S", string_prefix, command->string, field->name );
:D
sidenote: while i was in the neighborhood, i made a small change to _internal_vsnprintf(...) -- i prefer my functions to just crash the program when they fail, rather than returning some number i'll forget to check for and then spend hours trying to debug the downstream effects of so... int _internal_vsnprintf(...) { ... // // NOTE (copied from the printf docs) // \return The number of characters that COULD have been written // into the buffer, not counting the terminating null character. // A value equal or larger than count indicates truncation. // Only when the returned value is non-negative and // less than count, the result has been completely written. B32 success = (1 && (result >= 0) && ((size_t) result < maxlen) ); if (!success) { *((volatile S32 *) 0) = 0; // (crash) } return(result); }
1 after writing this blog post, i went ahead and just rolled my own printf from scratch -- it knows about my vector types ( %r3 for Real3, etc. )