From 8a4b54cb5993c3169d4eabe77400e0904872add1 Mon Sep 17 00:00:00 2001 From: Renato Florentino Garcia Date: Thu, 12 Oct 2023 15:07:47 -0300 Subject: [PATCH] Remove all Formatter and FormatterPack machinery. This is a work in progress effort to use a unique format string to many arguments. More on line with the fmt and std formatting libraries. --- README.md | 26 +-- icecream.hpp | 556 +++++++++++++++++--------------------------------- test/test.cpp | 37 +--- 3 files changed, 196 insertions(+), 423 deletions(-) diff --git a/README.md b/README.md index d84bbf4..ec64545 100644 --- a/README.md +++ b/README.md @@ -251,31 +251,11 @@ will print: ic| a: 0X2A, b: 0X14 - -The output formatting configuration is done wrapping a format string and the values with -the function `icecream::f_()`, like in: - -```C++ -using icecream::f_; -auto a = int{42}; - -IC(f_("X", a)); -IC(f_("0v#6x", 20, 30), 40, f_("*>6", a)); -``` - -that will print: - - ic| a: 2A - ic| 20: 0x0014, 30: 0x001e, 40: 40, a: ****42 - - -If the same formatting string should be applied to all the values on an `IC` macro call, -you can use the `IC_` macro as a shortcut. The code `IC(icecream::f_("#x", a, b))` can be -rewritten as `IC_("#x", a, b)`. +The same formatting string will be applied to all the values on an `IC` macro call. To configure the formating of [`IC_A`](#return-value-and-icecream-apply-macro) macro, -there are the macro `IC_A_`. It is just like `IC_A` but receives a formating string as its -first argument. The code: +there are the macro `IC_A_`. It is just like `IC_A` but receiving a formating string as +its first argument. The code: ```C++ IC_A_("#x", my_function, 10, 20); ``` diff --git a/icecream.hpp b/icecream.hpp index 8e3885d..bcffac4 100644 --- a/icecream.hpp +++ b/icecream.hpp @@ -126,15 +126,15 @@ #define ICECREAM_APPLY_S(ICM, N, S, F, ...) ICECREAM_APPLY_(F, ICM, N, S, __VA_ARGS__) #if defined(ICECREAM_LONG_NAME) - #define ICECREAM(...) ::icecream::detail::Dispatcher{__FILE__, __LINE__, ICECREAM_FUNCTION, #__VA_ARGS__}.ret(__VA_ARGS__) - #define ICECREAM0() ::icecream::detail::Dispatcher{__FILE__, __LINE__, ICECREAM_FUNCTION, ""}.ret() - #define ICECREAM_(S, ...) ICECREAM(::icecream::f_(S, __VA_ARGS__)) + #define ICECREAM(...) ICECREAM_("", __VA_ARGS__) + #define ICECREAM0() ::icecream::detail::Dispatcher{__FILE__, __LINE__, ICECREAM_FUNCTION, "", ""}.ret() + #define ICECREAM_(S, ...) ::icecream::detail::Dispatcher{__FILE__, __LINE__, ICECREAM_FUNCTION, S, #__VA_ARGS__}.ret(__VA_ARGS__) #define ICECREAM_A(...) ICECREAM_EXPAND(ICECREAM_APPLY(ICECREAM, ICECREAM_ARGS_SIZE(__VA_ARGS__), __VA_ARGS__)) #define ICECREAM_A_(S, ...) ICECREAM_EXPAND(ICECREAM_APPLY(ICECREAM_, ICECREAM_ARGS_SIZE(__VA_ARGS__), S, __VA_ARGS__)) #else - #define IC(...) ::icecream::detail::Dispatcher{__FILE__, __LINE__, ICECREAM_FUNCTION, #__VA_ARGS__}.ret(__VA_ARGS__) - #define IC0() ::icecream::detail::Dispatcher{__FILE__, __LINE__, ICECREAM_FUNCTION, ""}.ret() - #define IC_(S, ...) IC(::icecream::f_(S, __VA_ARGS__)) + #define IC(...) IC_("", __VA_ARGS__) + #define IC0() ::icecream::detail::Dispatcher{__FILE__, __LINE__, ICECREAM_FUNCTION, "", ""}.ret() + #define IC_(S, ...) ::icecream::detail::Dispatcher{__FILE__, __LINE__, ICECREAM_FUNCTION, S, #__VA_ARGS__}.ret(__VA_ARGS__) #define IC_A(...) ICECREAM_EXPAND(ICECREAM_APPLY(IC, ICECREAM_ARGS_SIZE(__VA_ARGS__), __VA_ARGS__)) #define IC_A_(S, ...) ICECREAM_EXPAND(ICECREAM_APPLY_S(IC_, ICECREAM_ARGS_SIZE(__VA_ARGS__), S, __VA_ARGS__)) #endif @@ -568,84 +568,172 @@ namespace icecream{ namespace detail return std::move(t); }; - // -------------------------------------------------- Formatter + // -------------------------------------------------- Tree - // Holds a variable and its formatting string. - template - struct Formatter - { - Formatter(std::string const& fmt_, T&& v_) - : fmt{fmt_} - , v{std::forward(v_)} - {} + // Needed to access the Icecream::show_c_string() method before the Icecream class + // declaration. + auto show_c_string() -> bool; - std::string fmt; - T&& v; - }; +#if defined(ICECREAM_DUMP_STRUCT_CLANG) + class Tree; + static inline auto parse_struct_dump(char const* format, ...) -> int; + static Tree* ds_this = nullptr; + static std::ostringstream* ds_buf = nullptr; +#endif - // Holds a tuple with one Formatter to each `vs...`, all of them having the same `fmt` string. - template - struct FormatterPack + // Builds an ostringstream and sets its state accordingly to `fmt` string + inline auto build_ostream(std::string const& fmt) -> std::tuple { - FormatterPack(std::string const& fmt, Ts&&... vs_) - : vs{Formatter(fmt, std::forward(vs_))...} - { - static_assert( - conjunction< - negation::type>>... - >::value, - "It is not possible to nest FormmaterPack's as in IC_(\"#\", 7, f_(\"#x\", 42))." - ); - } + // format_spec ::= [[fill]align][sign]["#"][width]["." precision][type] + // fill ::= + // align ::= "<" | ">" | "v" + // sign ::= "+" | "-" + // width ::= integer + // precision ::= integer + // type ::= "a" | "A" | "d" | "e" | "E" | "f" | "F" | "g" | "G" | "o" | "x" | "X" + // integer ::= digit+ + // digit ::= "0"..."9" - std::tuple...> vs; - }; + auto os = std::ostringstream {}; - template - using is_formatter_pack = is_instantiation::type>; + auto it = std::begin(fmt); + auto end_it = std::end(fmt); - template - auto drill_vars(FormatterPack&& fp, sequence) -> std::tuple - { - return std::tuple{std::forward(std::get(fp.vs).v) ...}; - } + if (it == end_it) return std::make_tuple(true, std::move(os)); - template - auto as_tuple(T&& t) -> std::tuple(t))> - { - return std::forward_as_tuple(std::forward(t)); - }; - - template - auto as_tuple(FormatterPack&& fp) -> std::tuple - { - using Seq = typename gen_sequence::type; - return drill_vars(std::move(fp), Seq{}); - }; + // [[fill]align] + { + auto fill_char = os.fill(); + if (*it != '<' && *it != '>' && *it != 'v') + { + auto la_it = it+1; + if (la_it != end_it && (*la_it == '<' || *la_it == '>' || *la_it == 'v')) + { + fill_char = *it; + ++it; + } + } + if (it != end_it && *it == '<') + { + os << std::left << std::setfill(fill_char); + ++it; + } + else if (it != end_it && *it == '>') + { + os << std::right << std::setfill(fill_char); + ++it; + } + else if (it != end_it && *it == 'v') + { + os << std::internal << std::setfill(fill_char); + ++it; + } + } + // [sign] + if (it != end_it && *it == '+') + { + os << std::showpos; + ++it; + } + else if (it != end_it && *it == '-') + { + os << std::noshowpos; + ++it; + } - // Receive a sequence of parameters and return a tuple with all them. If there is any - // FormatterPack's on that sequence, the variables inside them will be put on that - // tuple, instead of the Formmaterpack itself. - template - auto flatten_formatter_pack(Ts&&... t) -> decltype(std::tuple_cat(as_tuple(std::forward(t))...)) - { - return std::tuple_cat(as_tuple(std::forward(t))...); - } + // ["#"] + if (it != end_it && *it == '#') + { + os << std::showbase << std::showpoint; + ++it; + } + // [width] + { + auto b_it = it; + while (it != end_it && *it >= '0' && *it <= '9') ++it; + if (it != b_it) + os << std::setw(std::stoi(std::string(b_it, it))); + } - // -------------------------------------------------- Tree + // ["." precision] + if (it != end_it && *it == '.') + { + auto b_it = it+1; + auto p_it = b_it; + while (p_it != end_it && *p_it >= '0' && *p_it <= '9') ++p_it; + if (p_it != b_it) + { + os << std::setprecision(std::stoi(std::string(b_it, p_it))); + it = p_it; + } + } - // Needed to access the Icecream::show_c_string() method before the Icecream class - // declaration. - auto show_c_string() -> bool; + // [type] + if (it != end_it && *it == 'a') + { + os << std::hexfloat << std::nouppercase; + ++it; + } + else if (it != end_it && *it == 'A') + { + os << std::hexfloat << std::uppercase; + ++it; + } + else if (it != end_it && *it == 'd') + { + os << std::dec; + ++it; + } + else if (it != end_it && *it == 'e') + { + os << std::scientific << std::nouppercase; + ++it; + } + else if (it != end_it && *it == 'E') + { + os << std::scientific << std::uppercase; + ++it; + } + else if (it != end_it && *it == 'f') + { + os << std::fixed << std::nouppercase; + ++it; + } + else if (it != end_it && *it == 'F') + { + os << std::fixed << std::uppercase; + ++it; + } + else if (it != end_it && *it == 'g') + { + os << std::defaultfloat << std::nouppercase; + ++it; + } + else if (it != end_it && *it == 'G') + { + os << std::defaultfloat << std::uppercase; + ++it; + } + else if (it != end_it && *it == 'o') + { + os << std::oct; + ++it; + } + else if (it != end_it && *it == 'x') + { + os << std::hex << std::nouppercase; + ++it; + } + else if (it != end_it && *it == 'X') + { + os << std::hex << std::uppercase; + ++it; + } -#if defined(ICECREAM_DUMP_STRUCT_CLANG) - class Tree; - static inline auto parse_struct_dump(char const* format, ...) -> int; - static Tree* ds_this = nullptr; - static std::ostringstream* ds_buf = nullptr; -#endif + return std::make_tuple((it == end_it), std::move(os)); + } class Tree { @@ -698,160 +786,6 @@ namespace icecream{ namespace detail } content_; - // Builds an ostringstream and sets its state accordingly to `fmt` string - static auto build_ostream(std::string const& fmt) -> std::tuple - { - // format_spec ::= [[fill]align][sign]["#"][width]["." precision][type] - // fill ::= - // align ::= "<" | ">" | "v" - // sign ::= "+" | "-" - // width ::= integer - // precision ::= integer - // type ::= "a" | "A" | "d" | "e" | "E" | "f" | "F" | "g" | "G" | "o" | "x" | "X" - // integer ::= digit+ - // digit ::= "0"..."9" - - auto os = std::ostringstream {}; - - auto it = std::begin(fmt); - auto end_it = std::end(fmt); - - if (it == end_it) return std::make_tuple(true, std::move(os)); - - // [[fill]align] - { - auto fill_char = os.fill(); - if (*it != '<' && *it != '>' && *it != 'v') - { - auto la_it = it+1; - if (la_it != end_it && (*la_it == '<' || *la_it == '>' || *la_it == 'v')) - { - fill_char = *it; - ++it; - } - } - if (it != end_it && *it == '<') - { - os << std::left << std::setfill(fill_char); - ++it; - } - else if (it != end_it && *it == '>') - { - os << std::right << std::setfill(fill_char); - ++it; - } - else if (it != end_it && *it == 'v') - { - os << std::internal << std::setfill(fill_char); - ++it; - } - } - - // [sign] - if (it != end_it && *it == '+') - { - os << std::showpos; - ++it; - } - else if (it != end_it && *it == '-') - { - os << std::noshowpos; - ++it; - } - - // ["#"] - if (it != end_it && *it == '#') - { - os << std::showbase << std::showpoint; - ++it; - } - - // [width] - { - auto b_it = it; - while (it != end_it && *it >= '0' && *it <= '9') ++it; - if (it != b_it) - os << std::setw(std::stoi(std::string(b_it, it))); - } - - // ["." precision] - if (it != end_it && *it == '.') - { - auto b_it = it+1; - auto p_it = b_it; - while (p_it != end_it && *p_it >= '0' && *p_it <= '9') ++p_it; - if (p_it != b_it) - { - os << std::setprecision(std::stoi(std::string(b_it, p_it))); - it = p_it; - } - } - - // [type] - if (it != end_it && *it == 'a') - { - os << std::hexfloat << std::nouppercase; - ++it; - } - else if (it != end_it && *it == 'A') - { - os << std::hexfloat << std::uppercase; - ++it; - } - else if (it != end_it && *it == 'd') - { - os << std::dec; - ++it; - } - else if (it != end_it && *it == 'e') - { - os << std::scientific << std::nouppercase; - ++it; - } - else if (it != end_it && *it == 'E') - { - os << std::scientific << std::uppercase; - ++it; - } - else if (it != end_it && *it == 'f') - { - os << std::fixed << std::nouppercase; - ++it; - } - else if (it != end_it && *it == 'F') - { - os << std::fixed << std::uppercase; - ++it; - } - else if (it != end_it && *it == 'g') - { - os << std::defaultfloat << std::nouppercase; - ++it; - } - else if (it != end_it && *it == 'G') - { - os << std::defaultfloat << std::uppercase; - ++it; - } - else if (it != end_it && *it == 'o') - { - os << std::oct; - ++it; - } - else if (it != end_it && *it == 'x') - { - os << std::hex << std::nouppercase; - ++it; - } - else if (it != end_it && *it == 'X') - { - os << std::hex << std::uppercase; - ++it; - } - - return std::make_tuple((it == end_it), std::move(os)); - } - struct InnerTag {}; Tree(InnerTag, std::string leaf) @@ -961,18 +895,6 @@ namespace icecream{ namespace detail return Tree{InnerTag{}, std::move(text)}; } - template - explicit Tree(Formatter const& v) - : Tree {[&] - { - auto result_os = Tree::build_ostream(v.fmt); - if (std::get<0>(result_os)) - return Tree{v.v, std::move(std::get<1>(result_os))}; - else - return Tree::literal("*Error* on formatting string"); - }()} - {} - Tree( std::string&& open, std::string&& separator, @@ -1952,11 +1874,6 @@ namespace icecream{ namespace detail template struct is_printable: is_tree_argument {}; - template - struct is_printable&>: conjunction< - is_printable... - > {}; - // -------------------------------------------------- to_invocable @@ -2165,6 +2082,7 @@ namespace icecream{ namespace detail std::string const& file, int line, std::string const& function, + std::string const& format, std::vector const& arg_names, Ts&&... args ) -> void @@ -2196,7 +2114,7 @@ namespace icecream{ namespace detail else { auto const forest = Icecream::build_forest( - std::begin(arg_names), std::forward(args)... + format, std::begin(arg_names), std::forward(args)... ); this->print_forest(prefix, context, forest); } @@ -2410,72 +2328,9 @@ namespace icecream{ namespace detail } } - static - auto clean_variable_name(std::string const& var_name) -> std::string - { - auto b_it = std::begin(var_name); - auto e_it = std::end(var_name) - 1; - - auto par_count = int{0}; - - // Remove outer left parenthesis - while (*b_it == ' ' || *b_it == '\t' || *b_it == '\n' || *b_it == '(') - { - if (*b_it == '(') - par_count += 1; - ++b_it; - } - - // Remove outer right parenthesis - while (par_count > 0) - { - if (*e_it == ')') - par_count -= 1; - --e_it; - } - - // Remove right white spaces - while (*e_it == ' ' || *e_it == '\t' || *e_it == '\n') - --e_it; - - return std::string{b_it, e_it+1}; - } - - // Receive a string with a `icecream::f_("0v#4x", a, b, c)` call, and returns a vector with all the variable names. - // In this example, a vector with ["a", "b", "c"]. - static - auto split_variable_names(std::string const& var_name) -> std::vector - { - auto b_it = std::begin(var_name); - auto e_it = std::end(var_name) - 1; - - std::vector result; - - // Find format string/value splitting comma - { - auto nesting_deep = int{0}; - b_it = e_it; - do - { - if (*b_it == ')') - nesting_deep += 1; - else if (*b_it == '(') - nesting_deep -= 1; - else if (*b_it == ',' && nesting_deep == 1) - { - result.push_back(clean_variable_name(std::string{b_it+1, e_it})); - e_it = b_it; - } - - --b_it; - } while (nesting_deep > 0); - ++b_it; - } - - return std::vector{result.rbegin(), result.rend()}; - } auto build_forest( + std::string const& format, std::vector::const_iterator ) -> std::vector> { @@ -2495,45 +2350,32 @@ namespace icecream{ namespace detail template auto build_forest( + std::string const& format, std::vector::const_iterator arg_name, T&& arg_value, Ts&&... args_tail - ) -> - typename std::enable_if< - is_formatter_pack::value, - std::vector> - >::type + ) -> std::vector> { auto forest = Icecream::build_forest( - arg_name+1, std::forward(args_tail)... - ); - - Icecream::fill_forest_from_tuple( - Icecream::split_variable_names(Icecream::clean_variable_name(*arg_name)), - arg_value.vs, - forest + format, arg_name+1, std::forward(args_tail)... ); - return forest; - } + auto result_os = build_ostream(format); + if (std::get<0>(result_os)) + { + forest.emplace_back( + *arg_name, + Tree{std::move(arg_value), std::move(std::get<1>(result_os))} + ); + } + else + { + forest.emplace_back( + *arg_name, + Tree::literal("*Error* on formatting string") + ); + } - template - auto build_forest( - std::vector::const_iterator arg_name, - T&& arg_value, - Ts&&... args_tail - ) -> - typename std::enable_if< - !is_formatter_pack::value, - std::vector> - >::type - { - auto forest = Icecream::build_forest( - arg_name+1, std::forward(args_tail)... - ); - forest.emplace_back( - Icecream::clean_variable_name(*arg_name), - Tree{std::move(arg_value), std::ostringstream{}}); return forest; } }; @@ -2646,6 +2488,7 @@ namespace icecream std::string const& file, int line, std::string const& function, + std::string const& format, std::vector const& arg_names, Ts&&... args ) -> typename std::enable_if< @@ -2653,19 +2496,13 @@ namespace icecream >::type { detail::Icecream::instance().print( - file, line, function, arg_names, std::forward(args)... + file, line, function, format, arg_names, std::forward(args)... ); } }; static IcecreamAPI ic {}; - template - auto f_(std::string const& fmt, Ts&&... vs) -> detail::FormatterPack(vs))...> - { - return detail::FormatterPack(vs))...>{fmt, std::forward(vs)...}; - } - } // namespace icecream namespace icecream{ namespace detail @@ -2800,6 +2637,7 @@ namespace icecream{ namespace detail std::string const file; int line; std::string const function; + std::string const format; std::string const arg_names; // Used by compilers that expand an empyt __VA_ARGS__ in @@ -2808,11 +2646,13 @@ namespace icecream{ namespace detail std::string const& file_, int line_, std::string const& function_, + std::string const& format_, std::string const& arg_names_ ) : file {file_} , line {line_} , function {function_} + , format {format_} , arg_names {arg_names_} {} @@ -2821,11 +2661,13 @@ namespace icecream{ namespace detail Dispatcher( std::string const& file_, int line_, - std::string const& function_ + std::string const& function_, + std::string const& format_ ) : file {file_} , line {line_} , function {function_} + , format {format_} , arg_names {""} {} @@ -2833,15 +2675,15 @@ namespace icecream{ namespace detail auto print(Ts&&... args) -> void { auto arg_names = split_arguments(this->arg_names); - ::icecream::ic.print(file, line, function, arg_names, std::forward(args)...); + ::icecream::ic.print(file, line, function, format, arg_names, std::forward(args)...); } - // Return a std::tuple with all the args, flattening the content of any FormatterPack + // Return a std::tuple with all the args template - auto ret(Ts&&... args) -> decltype(flatten_formatter_pack(std::forward(args)...)) + auto ret(Ts&&... args) -> decltype(std::forward_as_tuple(std::forward(args)...)) { this->print(args...); - return flatten_formatter_pack(std::forward(args)...); + return std::forward_as_tuple(std::forward(args)...); } // Return the unique arg @@ -2852,22 +2694,6 @@ namespace icecream{ namespace detail return std::forward(arg); } - // Return the flattened unique arg of FormatterPack - template - auto ret(FormatterPack&& arg) -> T - { - this->print(arg); - return std::forward(std::get<0>(arg.vs).v); - } - - // Return a std::tuple with the content of the FormatterPack - template - auto ret(FormatterPack&& arg) -> std::tuple - { - this->print(arg); - return flatten_formatter_pack(std::move(arg)); - } - auto ret() -> void { this->print(); diff --git a/test/test.cpp b/test/test.cpp index 0fd9f47..02d3aa8 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -221,7 +221,7 @@ TEST_CASE("base") } { - IC((sum(40, 2)), i0 , (((sum(3, 5))))); + IC(sum(40, 2), i0 , sum(3, 5)); REQUIRE(str == "ic| sum(40, 2): 42, i0: 7, sum(3, 5): 8\n"); str.clear(); } @@ -285,8 +285,6 @@ TEST_CASE("return") auto str = std::string{}; icecream::ic.output(str); - using icecream::f_; - { REQUIRE(std::is_same::value); REQUIRE(IC(7) == 7); @@ -348,21 +346,6 @@ TEST_CASE("return") REQUIRE(IC_("#", 7, a, 3.14) == std::make_tuple(7, 30, 3.14)); } - { - auto a = int{10}; - auto const b = int{20}; - - REQUIRE( - std::is_same< - decltype(IC(f_("0v#4x", a, b), 49)), - std::tuple - >::value - ); - - auto&& v0 = IC(f_("0v#4x", a, b), 49); - REQUIRE(IC(f_("0v#4x", a, b), 49) == std::make_tuple(10, 20, 49)); - } - { REQUIRE(std::is_same::value); } @@ -1106,15 +1089,6 @@ TEST_CASE("formatting") auto str = std::string{}; icecream::ic.output(str); - using icecream::f_; - - { - auto v0 = int{42}; - IC(f_("#x", v0), 7); - REQUIRE(str == "ic| v0: 0x2a, 7: 7\n"); - str.clear(); - } - { auto v0 = int{42}; IC_("#o", v0, 7); @@ -1122,16 +1096,9 @@ TEST_CASE("formatting") str.clear(); } - { - auto v0 = int{42}; - IC( ((f_("#", (v0) ) )), 7); - REQUIRE(str == "ic| v0: 42, 7: 7\n"); - str.clear(); - } - { auto v0 = float{12.3456789}; - IC((f_("#A", v0))); + IC_("#A", v0); REQUIRE( ((str == "ic| v0: 0X1.8B0FCEP+3\n") || (str == "ic| v0: 0X1.8B0FCE0000000P+3\n")) // Visualstudio 2019 output