11#ifndef MICROFMT_HPP
22#define MICROFMT_HPP
33
4- // Copyright (c) 2024 Jeremy Rifkin; MIT License
5-
64#include < algorithm>
75#include < array>
86#include < cstdint>
9- #include < cstdio>
107#include < cstring>
118#include < iostream>
129#include < string>
1310#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L)
14- #include < string_view>
11+ #include < string_view>
1512#endif
1613#ifdef _MSC_VER
17- #include < intrin.h>
14+ #include < intrin.h>
1815#endif
1916
20- // {[[align][width]:[fill][base]]}
21- // width: number or {}
17+ // https://github.com/jeremy-rifkin/microfmt
18+ // Format: {[align][width][:[fill][base]]} # width: number or {}
2219
2320namespace microfmt {
2421 namespace detail {
25- #define STR2 (x ) #x
26- #define STR (x ) STR2(x)
27- #define MICROFMT_ASSERT (expr ) if (!(expr)) { \
28- throw std::runtime_error (" Microfmt check failed" __FILE__ " :" STR (__LINE__) " : " #expr); \
29- }
30-
3122 inline std::uint64_t clz (std::uint64_t value) {
3223 #ifdef _MSC_VER
3324 unsigned long out = 0 ;
@@ -60,7 +51,6 @@ namespace microfmt {
6051
6152 template <typename It> void do_write (std::string& out, It begin, It end, const format_options& options) {
6253 auto size = end - begin;
63- MICROFMT_ASSERT (size >= 0 );
6454 if (static_cast <std::size_t >(size) >= options.width ) {
6555 out.append (begin, end);
6656 } else {
@@ -100,13 +90,11 @@ namespace microfmt {
10090
10191 inline std::string to_string (std::uint64_t value, const format_options& options) {
10292 switch (options.base ) {
103- case ' d' : return std::to_string (value);
10493 case ' H' : return to_string<4 , 0xf >(value, " 0123456789ABCDEF" );
10594 case ' h' : return to_string<4 , 0xf >(value);
10695 case ' o' : return to_string<3 , 0x7 >(value);
10796 case ' b' : return to_string<1 , 0x1 >(value);
108- default :
109- MICROFMT_ASSERT (false );
97+ default : return std::to_string (value); // failure: decimal
11098 }
11199 }
112100
@@ -154,7 +142,7 @@ namespace microfmt {
154142 switch (value) {
155143 case value_type::int64_value: return static_cast <int >(int64_value);
156144 case value_type::uint64_value: return static_cast <int >(uint64_value);
157- default : MICROFMT_ASSERT ( false );
145+ default : return 0 ; // failure: just 0
158146 }
159147 }
160148
@@ -193,108 +181,84 @@ namespace microfmt {
193181 case value_type::c_string_value:
194182 do_write (out, c_string_value, c_string_value + std::strlen (c_string_value), options);
195183 break ;
196- default :
197- MICROFMT_ASSERT (false );
198- }
184+ } // failure: nop
199185 }
200186 };
201187
202- inline int parse_int (const char * begin, const char * end) {
203- int x = 0 ;
204- for (auto it = begin; it != end; it++) {
205- MICROFMT_ASSERT (isdigit (*it));
206- x *= 10 ;
207- x += *it - ' 0' ;
208- }
209- return x;
210- }
211-
212- template <std::size_t N>
213- std::string format (const char * fmt_begin, const char * fmt_end, std::array<format_value, N> args) {
188+ template <std::size_t N, typename It>
189+ std::string format (It fmt_begin, It fmt_end, std::array<format_value, N> args) {
214190 std::string str;
215191 std::size_t arg_i = 0 ;
216192 auto it = fmt_begin;
217193 auto peek = [&] (std::size_t dist) -> char { // 0 on failure
218- if (it != fmt_end) {
219- return *(it + dist);
220- } else {
221- return 0 ;
222- }
194+ return fmt_end - it > signed (dist) ? *(it + dist) : 0 ;
223195 };
224196 auto read_number = [&] () -> int { // -1 on failure
225197 auto scan = it;
198+ int num = 0 ;
226199 while (scan != fmt_end && isdigit (*scan)) {
200+ num *= 10 ;
201+ num += *scan - ' 0' ;
227202 scan++;
228203 }
229204 if (scan != it) {
230- int val = parse_int (it, scan);
231205 it = scan;
232- return val ;
206+ return num ;
233207 } else {
234208 return -1 ;
235209 }
236210 };
237- while (it != fmt_end) {
238- if (*it == ' {' ) {
239- if (peek (1 ) == ' {' ) {
240- // try to handle escape
241- str += ' {' ;
242- it++;
243- } else {
244- // parse format string
211+ for (; it != fmt_end; it++) {
212+ if ((*it == ' {' || *it == ' }' ) && peek (1 ) == *it) { // parse {{ and }} escapes
213+ it++;
214+ } else if (*it == ' {' && it + 1 != fmt_end) {
215+ auto saved_it = it;
216+ auto handle_formatter = [&] () {
245217 it++;
246- MICROFMT_ASSERT (it != fmt_end);
247218 format_options options;
248219 // try to parse alignment
249220 if (*it == ' <' || *it == ' >' ) {
250- options.align = *it == ' <' ? alignment::left : alignment::right;
251- it++;
221+ options.align = *it++ == ' <' ? alignment::left : alignment::right;
252222 }
253223 // try to parse width
254- auto width = read_number ();
224+ auto width = read_number (); // handles fmt_end check
255225 if (width != -1 ) {
256226 options.width = width;
257- } else if (*it == ' {' ) { // try to parse variable width
258- MICROFMT_ASSERT (peek (1 ) == ' }' );
227+ } else if (it != fmt_end && *it == ' {' ) { // try to parse variable width
228+ if (peek (1 ) != ' }' ) {
229+ return false ;
230+ }
259231 it += 2 ;
260- MICROFMT_ASSERT (arg_i < args.size ());
261- options.width = args[arg_i++].unwrap_int ();
232+ options.width = arg_i < args.size () ? args[arg_i++].unwrap_int () : 0 ;
262233 }
263234 // try to parse fill/base
264- if (*it == ' :' ) {
235+ if (it != fmt_end && *it == ' :' ) {
265236 it++;
266- // try to parse fill
267- if (*it != ' }' && peek (1 ) != ' }' ) {
268- // two chars before the }, treat as fill+base
237+ if (fmt_end - it > 1 && *it != ' }' && peek (1 ) != ' }' ) { // two chars before the }, fill+base
269238 options.fill = *it++;
270239 options.base = *it++;
271- } else if (*it != ' }' ) {
272- // one char before the }, treat as base if possible
240+ } else if (it != fmt_end && *it != ' }' ) { // one char before the }, just base
273241 if (*it == ' d' || *it == ' h' || *it == ' H' || *it == ' o' || *it == ' b' ) {
274242 options.base = *it++;
275243 } else {
276244 options.fill = *it++;
277245 }
278- } else {
279- MICROFMT_ASSERT (false );
280246 }
281247 }
282- MICROFMT_ASSERT (*it == ' }' );
283- MICROFMT_ASSERT (arg_i < args.size ());
284- args[arg_i++].write (str, options);
285- }
286- } else if (*it == ' }' ) {
287- // parse }} escape
288- if (peek (1 ) == ' }' ) {
289- str += ' }' ;
290- it++;
291- } else {
292- MICROFMT_ASSERT (false );
248+ if (it == fmt_end || *it != ' }' ) {
249+ return false ;
250+ }
251+ if (arg_i < args.size ()) {
252+ args[arg_i++].write (str, options);
253+ }
254+ return true ;
255+ };
256+ if (handle_formatter ()) {
257+ continue ; // If reached here, successfully parsed and wrote a formatter. Don't write *it.
293258 }
294- } else {
295- str += *it;
259+ it = saved_it; // go back
296260 }
297- it++ ;
261+ str += *it ;
298262 }
299263 return str;
300264 }
@@ -306,8 +270,9 @@ namespace microfmt {
306270 return detail::format<sizeof ...(args)>(fmt.begin (), fmt.end (), {detail::format_value (args)...});
307271 }
308272
273+ // working around an old msvc bug https://godbolt.org/z/88T8hrzzq mre: https://godbolt.org/z/drd8echbP
309274 inline std::string format (std::string_view fmt) {
310- return std::string (fmt);
275+ return detail::format< 1 > (fmt. begin (), fmt. end (), { detail::format_value ( 1 )} );
311276 }
312277 #endif
313278
@@ -316,7 +281,6 @@ namespace microfmt {
316281 return detail::format<sizeof ...(args)>(fmt, fmt + std::strlen (fmt), {detail::format_value (args)...});
317282 }
318283
319- // working around an old msvc bug https://godbolt.org/z/88T8hrzzq mre: https://godbolt.org/z/drd8echbP
320284 inline std::string format (const char * fmt) {
321285 return detail::format<1 >(fmt, fmt + std::strlen (fmt), {detail::format_value (1 )});
322286 }
0 commit comments