diff --git a/doc/corrade-changelog.dox b/doc/corrade-changelog.dox index cc6e8e2bb..d59e6d73c 100644 --- a/doc/corrade-changelog.dox +++ b/doc/corrade-changelog.dox @@ -208,6 +208,8 @@ namespace Corrade { and @relativeref{Utility::ConfigurationGroup,groups()} - New @ref Utility::Debug::invertedColor() output modifier for printing colored text with the foreground and background colors inverted +- New @ref Utility::Debug::hex output modifier for printing integers as + hexadecimal - New @ref Corrade::Utility::Json class for tokenizing and parsing JSON files into an immutable memory-efficient representation. See also [mosra/corrade#174](https://github.com/mosra/corrade/issues/174). diff --git a/doc/snippets/Utility.cpp b/doc/snippets/Utility.cpp index ab43bc7fb..a690ec95f 100644 --- a/doc/snippets/Utility.cpp +++ b/doc/snippets/Utility.cpp @@ -549,6 +549,13 @@ Utility::Debug{Utility::Debug::Flag::NoNewlineAtTheEnd} << "Hello!"; /* [Debug-modifiers-whitespace] */ } +{ +/* [Debug-modifiers-base] */ +// Prints 0xc0ffee +Utility::Debug{} << Utility::Debug::hex << 0xc0ffee; +/* [Debug-modifiers-base] */ +} + { /* [Debug-modifiers-colors] */ Utility::Debug{} diff --git a/src/Corrade/Utility/Debug.cpp b/src/Corrade/Utility/Debug.cpp index 124404eda..fbab46719 100644 --- a/src/Corrade/Utility/Debug.cpp +++ b/src/Corrade/Utility/Debug.cpp @@ -365,26 +365,26 @@ void Debug::resetColor(Debug& debug) { debug.resetColorInternal(); } -namespace { enum: unsigned char { PublicFlagMask = 0x1f }; } +namespace { enum: unsigned short { PublicFlagMask = 0x00ff }; } auto Debug::flags() const -> Flags { - return Flag(static_cast(_flags) & PublicFlagMask); + return Flag(static_cast(_flags) & PublicFlagMask); } void Debug::setFlags(Flags flags) { - _flags = InternalFlag(static_cast(flags)) | - InternalFlag(static_cast(_flags) & ~PublicFlagMask); + _flags = InternalFlag(static_cast(flags)) | + InternalFlag(static_cast(_flags) & ~PublicFlagMask); } auto Debug::immediateFlags() const -> Flags { - return Flag(static_cast(_immediateFlags) & PublicFlagMask) | - Flag(static_cast(_flags) & PublicFlagMask); + return Flag(static_cast(_immediateFlags) & PublicFlagMask) | + Flag(static_cast(_flags) & PublicFlagMask); } void Debug::setImmediateFlags(Flags flags) { /* unlike _flags, _immediateFlags doesn't contain any internal flags so no need to preserve these */ - _immediateFlags = InternalFlag(static_cast(flags)); + _immediateFlags = InternalFlag(static_cast(flags)); } std::ostream* Debug::defaultOutput() { return &std::cout; } @@ -452,7 +452,7 @@ bool Debug::isTty() { return isTty(debugGlobals.output); } bool Warning::isTty() { return Debug::isTty(debugGlobals.warningOutput); } bool Error::isTty() { return Debug::isTty(debugGlobals.errorOutput); } -Debug::Debug(std::ostream* const output, const Flags flags): _flags{InternalFlag(static_cast(flags))}, _immediateFlags{InternalFlag::NoSpace} { +Debug::Debug(std::ostream* const output, const Flags flags): _flags{InternalFlag(static_cast(flags))}, _immediateFlags{InternalFlag::NoSpace} { /* Save previous global output and replace it with current one */ _previousGlobalOutput = debugGlobals.output; debugGlobals.output = _output = output; @@ -565,21 +565,30 @@ template Debug& Debug::print(const T& value) { } #endif - /* Separate values with spaces if enabled; reset all internal flags after */ + /* Separate values with spaces if enabled */ if(!((_immediateFlags|_flags) & InternalFlag::NoSpace)) *_output << ' '; - _immediateFlags = {}; + /* Print the next value as hexadecimal if enabled */ + /** @todo this does strange crap for negative values (printing them as + unsigned), revisit once iostreams are not used anymore */ + if(((_immediateFlags|_flags) & InternalFlag::Hex) && std::is_integral::value) + *_output << "0x" << std::hex; toStream(*_output, value); + /* Reset the hexadecimal printing back if it was enabled */ + if(((_immediateFlags|_flags) & InternalFlag::Hex) && std::is_integral::value) + *_output << std::dec; + + /* Reset all internal flags after */ + _immediateFlags = {}; + _flags |= InternalFlag::ValueWritten; return *this; } Debug& Debug::operator<<(const void* const value) { - std::ostringstream o; - o << "0x" << std::hex << reinterpret_cast(value); - return print(o.str()); + return *this << hex << reinterpret_cast(value); } Debug& Debug::operator<<(const char* value) { return print(value); } @@ -698,11 +707,13 @@ Debug& operator<<(Debug& debug, Debug::Flag value) { _c(NoSpace) _c(Packed) _c(Color) + /* Space reserved for Bin and Oct */ + _c(Hex) #undef _c /* LCOV_EXCL_STOP */ } - return debug << "Utility::Debug::Flag(" << Debug::nospace << reinterpret_cast(static_cast(char(value))) << Debug::nospace << ")"; + return debug << "Utility::Debug::Flag(" << Debug::nospace << reinterpret_cast(static_cast(value)) << Debug::nospace << ")"; } Debug& operator<<(Debug& debug, Debug::Flags value) { @@ -711,7 +722,9 @@ Debug& operator<<(Debug& debug, Debug::Flags value) { Debug::Flag::DisableColors, Debug::Flag::NoSpace, Debug::Flag::Packed, - Debug::Flag::Color}); + Debug::Flag::Color, + /* Space reserved for Bin and Oct */ + Debug::Flag::Hex}); } #endif diff --git a/src/Corrade/Utility/Debug.h b/src/Corrade/Utility/Debug.h index 1b70b0578..cd1eafe03 100644 --- a/src/Corrade/Utility/Debug.h +++ b/src/Corrade/Utility/Debug.h @@ -115,6 +115,14 @@ newline at the end --- use @ref Flag::NoNewlineAtTheEnd or the @ref nospace @snippet Utility.cpp Debug-modifiers-whitespace +@subsection Utility-Debug-modifiers-base Printing numbers in a different base + +With @ref Flag::Hex or the @ref hex modifier, integers will be printed as +hexadecimal. Pointer values are printed as hexadecimal always, cast them to an +integer type to print them as decimal. + +@snippet Utility.cpp Debug-modifiers-base + @subsection Utility-Debug-modifiers-colors Colored output It is possible to color the output using @ref color(), @ref boldColor() and @@ -207,7 +215,7 @@ class CORRADE_UTILITY_EXPORT Debug { * * @see @ref Flags, @ref Debug(Flags) */ - enum class Flag: unsigned char { + enum class Flag: unsigned short { /** Don't put newline at the end on destruction */ NoNewlineAtTheEnd = 1 << 0, @@ -237,7 +245,18 @@ class CORRADE_UTILITY_EXPORT Debug { * Print colored values as colored squares in the terminal. * @see @ref color, @ref operator<<(unsigned char) */ - Color = 1 << 4 + Color = 1 << 4, + + /* Bit 5 and 6 reserved for Bin and Oct */ + + /** + * Print integer values as lowercase hexadecimal prefixed with + * `0x`, e.g. @cb{.shell-session} 0xc0ffee @ce instead of + * @cb{.shell-session} 12648430 @ce. + * @see @ref hex, @ref operator<<(const void*) + * @m_since_latest + */ + Hex = 1 << 7 /* When adding values, don't forget to adapt InternalFlag as well and update PublicFlagMask in Debug.cpp */ @@ -463,6 +482,19 @@ class CORRADE_UTILITY_EXPORT Debug { debug._immediateFlags |= InternalFlag::Color; } + /** + * @brief Print the next value as hexadecimal + * @m_since_latest + * + * If the next value is integer, it's printed as lowercase hexadecimal + * prefixed with `0x` e.g. @cb{.shell-session} 0xc0ffee @ce instead of + * @cb{.shell-session} 12648430 @ce. + * @see @ref Flag::Hex, @ref operator<<(const void*) + */ + static void hex(Debug& debug) { + debug._immediateFlags |= InternalFlag::Hex; + } + /** * @brief Debug output modification * @@ -646,10 +678,13 @@ class CORRADE_UTILITY_EXPORT Debug { /** * @brief Print a pointer value to debug output * - * The value is printed in lowercase hexadecimal, for example - * @cb{.shell-session} 0xdeadbeef @ce. + * The value is printed in lowercase hexadecimal prefixed with `0x`, + * for example @cb{.shell-session} 0xdeadbeef @ce. Equivalent to + * enabling @ref Flag::Hex or using the @ref hex modifier and printing + * @cpp reinterpret_cast(value) @ce instead of + * @cpp value @ce. */ - Debug& operator<<(const void* value); /**< @overload */ + Debug& operator<<(const void* value); /** * @brief Print a boolean value to debug output @@ -756,15 +791,18 @@ class CORRADE_UTILITY_EXPORT Debug { #endif std::ostream* _output; - enum class InternalFlag: unsigned char { - /* Values compatible with Flag enum */ + enum class InternalFlag: unsigned short { + /* Values matching the Flag enum */ NoNewlineAtTheEnd = 1 << 0, DisableColors = 1 << 1, NoSpace = 1 << 2, Packed = 1 << 3, Color = 1 << 4, - ValueWritten = 1 << 5, - ColorWritten = 1 << 6 + /* Bit 5 and 6 reserved for Bin and Oct */ + Hex = 1 << 7, + + ValueWritten = 1 << 8, + ColorWritten = 1 << 9 }; typedef Containers::EnumSet InternalFlags; @@ -775,7 +813,7 @@ class CORRADE_UTILITY_EXPORT Debug { InternalFlags _flags; InternalFlags _immediateFlags; - /* 2 / 6 bytes free */ + /* 0 / 4 bytes free */ private: #ifdef CORRADE_SOURCE_LOCATION_BUILTINS_SUPPORTED diff --git a/src/Corrade/Utility/Test/DebugTest.cpp b/src/Corrade/Utility/Test/DebugTest.cpp index 2fd65e012..3b6977787 100644 --- a/src/Corrade/Utility/Test/DebugTest.cpp +++ b/src/Corrade/Utility/Test/DebugTest.cpp @@ -31,6 +31,7 @@ #include #include +#include "Corrade/Containers/Pair.h" #include "Corrade/Containers/String.h" #include "Corrade/Containers/StringView.h" #include "Corrade/TestSuite/Tester.h" @@ -79,6 +80,8 @@ struct DebugTest: TestSuite::Tester { void colorsNoOutput(); void colorsScoped(); + void hex(); + void valueAsColor(); void valueAsColorColorsDisabled(); @@ -152,6 +155,8 @@ DebugTest::DebugTest() { &DebugTest::colorsNoOutput, &DebugTest::colorsScoped, + &DebugTest::hex, + &DebugTest::valueAsColor, &DebugTest::valueAsColorColorsDisabled, @@ -809,6 +814,88 @@ void DebugTest::colorsScoped() { #endif } +void DebugTest::hex() { + /* Local hex modifier, applied once */ + { + std::ostringstream out; + + { + Debug d{&out}; + d << "Values"; + CORRADE_VERIFY(!(d.flags() & Debug::Flag::Hex)); + CORRADE_VERIFY(!(d.immediateFlags() & Debug::Flag::Hex)); + + d << Debug::hex; + CORRADE_VERIFY(!(d.flags() & Debug::Flag::Hex)); + CORRADE_VERIFY((d.immediateFlags() & Debug::Flag::Hex)); + + d << 0xc0ffee; + CORRADE_VERIFY(!(d.flags() & Debug::Flag::Hex)); + CORRADE_VERIFY(!(d.immediateFlags() & Debug::Flag::Hex)); + + d << "and" << 16; + } + + CORRADE_COMPARE(out.str(), "Values 0xc0ffee and 16\n"); + } + + /* Global hex modifier, applied always */ + { + std::ostringstream out; + { + Debug d{&out, Debug::Flag::Hex}; + CORRADE_VERIFY((d.flags() & Debug::Flag::Hex)); + CORRADE_VERIFY((d.immediateFlags() & Debug::Flag::Hex)); + + /* Should work for any integer type without truncating, 0x should + be printed for 0 as well */ + d << 0xfedcba9876543210ull << 0xcdu << static_cast(0x13) << 0x0; + CORRADE_VERIFY((d.flags() & Debug::Flag::Hex)); + CORRADE_VERIFY((d.immediateFlags() & Debug::Flag::Hex)); + + /* Shouldn't be applied to non-integer types but should still stay + present for any that may come after */ + d << "yes" << 3.5f << false << 0xabc << U'\xabc'; + CORRADE_VERIFY((d.flags() & Debug::Flag::Hex)); + CORRADE_VERIFY((d.immediateFlags() & Debug::Flag::Hex)); + + /* Printing pointers applies it implicitly, check it doesn't + cause 0x to be printed twice or the flag reset after */ + d << nullptr << reinterpret_cast(std::size_t{0xc0ffee}) << 0x356; + CORRADE_VERIFY((d.flags() & Debug::Flag::Hex)); + CORRADE_VERIFY((d.immediateFlags() & Debug::Flag::Hex)); + } + + CORRADE_COMPARE(out.str(), + "0xfedcba9876543210 0xcd 0x13 0x0 " + "yes 3.5 false 0xabc U+0ABC " + "nullptr 0xc0ffee 0x356\n"); + } + + /* Negative values should have - before the 0x. Well, ideally, if iostreams + weren't irreparably broken in the first place, printing everything as + unsigned. */ + { + std::ostringstream out; + Debug{&out, Debug::Flag::Hex} << -0x356 << -0x1ll; + + { + CORRADE_EXPECT_FAIL("This doesn't work as expected with std::hex anyway, won't bother fixing until iostreams are dropped."); + CORRADE_COMPARE(out.str(), "-0x356 -0x1\n"); + } + + CORRADE_COMPARE(out.str(), "0xfffffcaa 0xffffffffffffffff\n"); + } + + /* Nested values should be printed as hex too, but it should be reset + after */ + { + std::ostringstream out; + Debug{&out} << Debug::hex << Containers::pair(0xab, Containers::arrayView({0xcd, 0x13})) << 1234; + CORRADE_COMPARE(out.str(), "{0xab, {0xcd, 0x13}} 1234\n"); + } +} + void DebugTest::valueAsColor() { Debug{} << "The following should be shades of gray:"; @@ -1066,15 +1153,15 @@ void DebugTest::debugColor() { void DebugTest::debugFlag() { std::ostringstream out; - Debug(&out) << Debug::Flag::NoNewlineAtTheEnd << Debug::Flag(0xde); - CORRADE_COMPARE(out.str(), "Utility::Debug::Flag::NoNewlineAtTheEnd Utility::Debug::Flag(0xde)\n"); + Debug(&out) << Debug::Flag::NoNewlineAtTheEnd << Debug::Flag(0xcafe); + CORRADE_COMPARE(out.str(), "Utility::Debug::Flag::NoNewlineAtTheEnd Utility::Debug::Flag(0xcafe)\n"); } void DebugTest::debugFlags() { std::ostringstream out; - Debug(&out) << (Debug::Flag::NoNewlineAtTheEnd|Debug::Flag::Packed) << Debug::Flags{}; - CORRADE_COMPARE(out.str(), "Utility::Debug::Flag::NoNewlineAtTheEnd|Utility::Debug::Flag::Packed Utility::Debug::Flags{}\n"); + Debug(&out) << (Debug::Flag::NoNewlineAtTheEnd|Debug::Flag::Packed|Debug::Flag(0xb000)) << Debug::Flags{}; + CORRADE_COMPARE(out.str(), "Utility::Debug::Flag::NoNewlineAtTheEnd|Utility::Debug::Flag::Packed|Utility::Debug::Flag(0xb000) Utility::Debug::Flags{}\n"); } #ifndef CORRADE_TARGET_EMSCRIPTEN diff --git a/src/Corrade/Utility/Test/JsonTest.cpp b/src/Corrade/Utility/Test/JsonTest.cpp index 65b7268f1..9b832f615 100644 --- a/src/Corrade/Utility/Test/JsonTest.cpp +++ b/src/Corrade/Utility/Test/JsonTest.cpp @@ -4518,29 +4518,13 @@ void JsonTest::constructMove() { void JsonTest::debugTokenType() { std::ostringstream out; Debug{&out} << JsonToken::Type::Number << JsonToken::Type(0xdeadbabedeadbabe); - { - #ifdef CORRADE_TARGET_32BIT - CORRADE_EXPECT_FAIL("Debug has shitty hex printing currently, using just the low 32 bits on 32-bit platforms."); - #endif - CORRADE_COMPARE(out.str(), "Utility::JsonToken::Type::Number Utility::JsonToken::Type(0xdeadbabedeadbabe)\n"); - } - #ifdef CORRADE_TARGET_32BIT - CORRADE_COMPARE(out.str(), "Utility::JsonToken::Type::Number Utility::JsonToken::Type(0xdeadbabe)\n"); - #endif + CORRADE_COMPARE(out.str(), "Utility::JsonToken::Type::Number Utility::JsonToken::Type(0xdeadbabedeadbabe)\n"); } void JsonTest::debugTokenParsedType() { std::ostringstream out; Debug{&out} << JsonToken::ParsedType::UnsignedInt << JsonToken::ParsedType(0xdeadbabedeadbabeull); - { - #ifdef CORRADE_TARGET_32BIT - CORRADE_EXPECT_FAIL("Debug has shitty hex printing currently, using just the low 32 bits on 32-bit platforms."); - #endif - CORRADE_COMPARE(out.str(), "Utility::JsonToken::ParsedType::UnsignedInt Utility::JsonToken::ParsedType(0xdeadbabedeadbabe)\n"); - } - #ifdef CORRADE_TARGET_32BIT - CORRADE_COMPARE(out.str(), "Utility::JsonToken::ParsedType::UnsignedInt Utility::JsonToken::ParsedType(0xdeadbabe)\n"); - #endif + CORRADE_COMPARE(out.str(), "Utility::JsonToken::ParsedType::UnsignedInt Utility::JsonToken::ParsedType(0xdeadbabedeadbabe)\n"); } }}}}