Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support multiple arguments #94

Merged
merged 4 commits into from
May 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,19 @@ vcpkg install dbg-macro

## Advanced features

### Multiple arguments

You can pass multiple arguments to the `dbg(…)` macro. The output of
`dbg(x, y, z)` is same as `dbg(x); dbg(y); dbg(z);`:
``` c++
dbg(42, "hello world", false);
```

Note that you have to wrap "unprotected commas" in parentheses:
```c++
dbg("a vector:", (std::vector<int>{2, 3, 4}));
```

### Hexadecimal, octal and binary format

If you want to format integers in hexadecimal, octal or binary representation, you can
Expand Down
145 changes: 117 additions & 28 deletions dbg.h
Original file line number Diff line number Diff line change
Expand Up @@ -622,48 +622,81 @@ inline bool pretty_print(std::ostream& stream,

#endif

template <typename T, typename... U>
struct last {
using type = typename last<U...>::type;
};

template <typename T>
struct last<T> {
using type = T;
};

template <typename... T>
using last_t = typename last<T...>::type;

class DebugOutput {
public:
DebugOutput(const char* filepath,
int line,
const char* function_name,
const char* expression)
: m_use_colorized_output(isColorizedOutputEnabled()),
m_filepath(filepath),
m_line(line),
m_function_name(function_name),
m_expression(expression) {
const std::size_t path_length = m_filepath.length();
// Helper alias to avoid obscure type `const char* const*` in signature.
using expr_t = const char*;

DebugOutput(const char* filepath, int line, const char* function_name)
: m_use_colorized_output(isColorizedOutputEnabled()) {
std::string path = filepath;
const std::size_t path_length = path.length();
if (path_length > MAX_PATH_LENGTH) {
m_filepath = ".." + m_filepath.substr(path_length - MAX_PATH_LENGTH,
MAX_PATH_LENGTH);
path = ".." + path.substr(path_length - MAX_PATH_LENGTH, MAX_PATH_LENGTH);
}
std::stringstream ss;
ss << ansi(ANSI_DEBUG) << "[" << path << ":" << line << " ("
<< function_name << ")] " << ansi(ANSI_RESET);
m_location = ss.str();
}

template <typename... T>
auto print(std::initializer_list<expr_t> exprs,
std::initializer_list<std::string> types,
T&&... values) -> last_t<T...> {
if (exprs.size() != sizeof...(values)) {
std::cerr
<< m_location << ansi(ANSI_WARN)
<< "The number of arguments mismatch, please check unprotected comma"
<< ansi(ANSI_RESET) << std::endl;
}
return print_impl(exprs.begin(), types.begin(), std::forward<T>(values)...);
}

private:
template <typename T>
T&& print(const std::string& type, T&& value) const {
T&& print_impl(const expr_t* expr, const std::string* type, T&& value) {
const T& ref = value;
std::stringstream stream_value;
const bool print_expr_and_type = pretty_print(stream_value, ref);

std::stringstream output;
output << ansi(ANSI_DEBUG) << "[" << m_filepath << ":" << m_line << " ("
<< m_function_name << ")] " << ansi(ANSI_RESET);
output << m_location;
if (print_expr_and_type) {
output << ansi(ANSI_EXPRESSION) << m_expression << ansi(ANSI_RESET)
<< " = ";
output << ansi(ANSI_EXPRESSION) << *expr << ansi(ANSI_RESET) << " = ";
}
output << ansi(ANSI_VALUE) << stream_value.str() << ansi(ANSI_RESET);
if (print_expr_and_type) {
output << " (" << ansi(ANSI_TYPE) << type << ansi(ANSI_RESET) << ")";
output << " (" << ansi(ANSI_TYPE) << *type << ansi(ANSI_RESET) << ")";
}
output << std::endl;
std::cerr << output.str();

return std::forward<T>(value);
}

private:
template <typename T, typename... U>
auto print_impl(const expr_t* exprs,
const std::string* types,
T&& value,
U&&... rest) -> last_t<T, U...> {
print_impl(exprs, types, std::forward<T>(value));
return print_impl(exprs + 1, types + 1, std::forward<U>(rest)...);
}

const char* ansi(const char* code) const {
if (m_use_colorized_output) {
return code;
Expand All @@ -674,15 +707,13 @@ class DebugOutput {

const bool m_use_colorized_output;

std::string m_filepath;
const int m_line;
const std::string m_function_name;
const std::string m_expression;
std::string m_location;

static constexpr std::size_t MAX_PATH_LENGTH = 20;

static constexpr const char* const ANSI_EMPTY = "";
static constexpr const char* const ANSI_DEBUG = "\x1b[02m";
static constexpr const char* const ANSI_WARN = "\x1b[33m";
static constexpr const char* const ANSI_EXPRESSION = "\x1b[36m";
static constexpr const char* const ANSI_VALUE = "\x1b[01m";
static constexpr const char* const ANSI_TYPE = "\x1b[32m";
Expand All @@ -696,14 +727,72 @@ T&& identity(T&& t) {
return std::forward<T>(t);
}

template <typename T, typename... U>
auto identity(T&&, U&&... u) -> last_t<U...> {
return identity(std::forward<U>(u)...);
}

} // namespace dbg

#ifndef DBG_MACRO_DISABLE
// We use a variadic macro to support commas inside expressions (e.g.
// initializer lists):
#define dbg(...) \
dbg::DebugOutput(__FILE__, __LINE__, __func__, #__VA_ARGS__) \
.print(dbg::type_name<decltype(__VA_ARGS__)>(), (__VA_ARGS__))

// Force expanding argument with commas for MSVC, ref:
// https://stackoverflow.com/questions/35210637/macro-expansion-argument-with-commas
// Note that "args" should be a tuple with parentheses, such as "(e1, e2, ...)".
#define DBG_IDENTITY(x) x
#define DBG_CALL(fn, args) DBG_IDENTITY(fn args)

#define DBG_CAT_IMPL(_1, _2) _1##_2
#define DBG_CAT(_1, _2) DBG_CAT_IMPL(_1, _2)

#define DBG_16TH_IMPL(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, \
_14, _15, _16, ...) \
_16
#define DBG_16TH(args) DBG_CALL(DBG_16TH_IMPL, args)
#define DBG_NARG(...) \
DBG_16TH((__VA_ARGS__, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))

// DBG_VARIADIC_CALL(fn, data, e1, e2, ...) => fn_N(data, (e1, e2, ...))
#define DBG_VARIADIC_CALL(fn, data, ...) \
DBG_CAT(fn##_, DBG_NARG(__VA_ARGS__))(data, (__VA_ARGS__))

// (e1, e2, e3, ...) => e1
#define DBG_HEAD_IMPL(_1, ...) _1
#define DBG_HEAD(args) DBG_CALL(DBG_HEAD_IMPL, args)

// (e1, e2, e3, ...) => (e2, e3, ...)
#define DBG_TAIL_IMPL(_1, ...) (__VA_ARGS__)
#define DBG_TAIL(args) DBG_CALL(DBG_TAIL_IMPL, args)

#define DBG_MAP_1(fn, args) DBG_CALL(fn, args)
#define DBG_MAP_2(fn, args) fn(DBG_HEAD(args)), DBG_MAP_1(fn, DBG_TAIL(args))
#define DBG_MAP_3(fn, args) fn(DBG_HEAD(args)), DBG_MAP_2(fn, DBG_TAIL(args))
#define DBG_MAP_4(fn, args) fn(DBG_HEAD(args)), DBG_MAP_3(fn, DBG_TAIL(args))
#define DBG_MAP_5(fn, args) fn(DBG_HEAD(args)), DBG_MAP_4(fn, DBG_TAIL(args))
#define DBG_MAP_6(fn, args) fn(DBG_HEAD(args)), DBG_MAP_5(fn, DBG_TAIL(args))
#define DBG_MAP_7(fn, args) fn(DBG_HEAD(args)), DBG_MAP_6(fn, DBG_TAIL(args))
#define DBG_MAP_8(fn, args) fn(DBG_HEAD(args)), DBG_MAP_7(fn, DBG_TAIL(args))
#define DBG_MAP_9(fn, args) fn(DBG_HEAD(args)), DBG_MAP_8(fn, DBG_TAIL(args))
#define DBG_MAP_10(fn, args) fn(DBG_HEAD(args)), DBG_MAP_9(fn, DBG_TAIL(args))
#define DBG_MAP_11(fn, args) fn(DBG_HEAD(args)), DBG_MAP_10(fn, DBG_TAIL(args))
#define DBG_MAP_12(fn, args) fn(DBG_HEAD(args)), DBG_MAP_11(fn, DBG_TAIL(args))
#define DBG_MAP_13(fn, args) fn(DBG_HEAD(args)), DBG_MAP_12(fn, DBG_TAIL(args))
#define DBG_MAP_14(fn, args) fn(DBG_HEAD(args)), DBG_MAP_13(fn, DBG_TAIL(args))
#define DBG_MAP_15(fn, args) fn(DBG_HEAD(args)), DBG_MAP_14(fn, DBG_TAIL(args))
#define DBG_MAP_16(fn, args) fn(DBG_HEAD(args)), DBG_MAP_15(fn, DBG_TAIL(args))

// DBG_MAP(fn, e1, e2, e3, ...) => fn(e1), fn(e2), fn(e3), ...
#define DBG_MAP(fn, ...) DBG_VARIADIC_CALL(DBG_MAP, fn, __VA_ARGS__)

#define DBG_STRINGIFY_IMPL(x) #x
#define DBG_STRINGIFY(x) DBG_STRINGIFY_IMPL(x)

#define DBG_TYPE_NAME(x) dbg::type_name<decltype(x)>()

#define dbg(...) \
dbg::DebugOutput(__FILE__, __LINE__, __func__) \
.print({DBG_MAP(DBG_STRINGIFY, __VA_ARGS__)}, \
{DBG_MAP(DBG_TYPE_NAME, __VA_ARGS__)}, __VA_ARGS__)
#else
#define dbg(...) dbg::identity(__VA_ARGS__)
#endif // DBG_MACRO_DISABLE
Expand Down
30 changes: 30 additions & 0 deletions tests/basic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,36 @@ TEST_CASE("side effects") {
CHECK(x == 2);
}

TEST_CASE("multiple arguments") {
SECTION("output format") {
// The output of dbg(x, y) should be same as dbg(x); dbg(y).
std::stringstream ss;
const auto orig_buf = std::cerr.rdbuf(ss.rdbuf());
// Put multiple statements in the same line to get exactly same output.
// clang-format off
dbg(42); dbg("test"); dbg(42, "test");
// clang-format on
std::cerr.rdbuf(orig_buf);

std::string lines[4];
for (int i = 0; i < 4; i++) {
std::getline(ss, lines[i]);
}
CHECK(lines[0] == lines[2]); // output for 42
CHECK(lines[1] == lines[3]); // output for "test"
}

SECTION("expression") {
// It should return the last expression.
int x = dbg(1, 2, 1 + 2);
CHECK(x == 3);

// Wrap unprotected commas with parenthesis.
x = dbg(1, (std::vector<int>{2, 3, 4}), 5);
CHECK(x == 5);
}
}

TEST_CASE("pretty_print") {
SECTION("primitive types") {
CHECK(pretty_print(3) == "3");
Expand Down
12 changes: 8 additions & 4 deletions tests/demo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ int main() {
dbg(9 + 33);
dbg(test_string + " world");

dbg("====== multiple arguments");

dbg(test_int, (std::vector<int>{2, 3, 4}), test_string);

dbg("====== containers");

const std::vector<int> dummy_vec_int{3, 2, 3};
Expand All @@ -80,7 +84,7 @@ int main() {
std::vector<bool> vec_bools{true, true, false, false, false, true, false};
dbg(vec_bools);

dbg(std::vector<int>{0, 1, 0, 1});
dbg((std::vector<int>{0, 1, 0, 1}));

const std::array<int, 2> dummy_array{{0, 4}};
dbg(dummy_array);
Expand All @@ -107,14 +111,14 @@ int main() {
dbg(dbg::bin(static_cast<uint8_t>(negative_five)));

dbg("====== std::tuple and std::pair");
dbg(std::tuple<std::string, int, float>{"Hello", 7, 3.14f});
dbg(std::pair<std::string, int>{"Hello", 7});
dbg((std::tuple<std::string, int, float>{"Hello", 7, 3.14f}));
dbg((std::pair<std::string, int>{"Hello", 7}));

#if DBG_MACRO_CXX_STANDARD >= 17
dbg("====== Sum types");
dbg(std::make_optional<bool>(false));

dbg(std::variant<int, std::string>{"test"});
dbg((std::variant<int, std::string>{"test"}));
#endif

dbg("====== function name tests");
Expand Down