From 1e30544cff64f87e8ebe76c7ccea194f43425830 Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Tue, 4 Mar 2025 16:17:16 -0500 Subject: [PATCH] print statements --- CMakeLists.txt | 2 +- include/blt/logging/fmt_tokenizer.h | 34 +++++++- include/blt/logging/logging.h | 27 ++++-- include/blt/meta/is_streamable.h | 2 + src/blt/logging/fmt_tokenizer.cpp | 119 +++++++++++++++++++++++-- src/blt/logging/logging.cpp | 38 +++++--- tests/logger_tests.cpp | 129 +++++++++++++++++++++------- 7 files changed, 291 insertions(+), 60 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4f359be..54387ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 5.1.8) +set(BLT_VERSION 5.1.9) set(BLT_TARGET BLT) diff --git a/include/blt/logging/fmt_tokenizer.h b/include/blt/logging/fmt_tokenizer.h index 6e95c18..8f1cffe 100644 --- a/include/blt/logging/fmt_tokenizer.h +++ b/include/blt/logging/fmt_tokenizer.h @@ -35,7 +35,17 @@ namespace blt::logging DOT, MINUS, PLUS, - POUND + POUND, + LEFT_CHEVRON, + RIGHT_CHEVRON, + CARET + }; + + enum class fmt_align_t : u8 + { + LEFT, + CENTER, + RIGHT }; enum class fmt_sign_t : u8 @@ -67,7 +77,8 @@ namespace blt::logging i64 precision = -1; fmt_type_t type = fmt_type_t::UNSPECIFIED; fmt_sign_t sign = fmt_sign_t::MINUS; - bool leading_zeros = false; + fmt_align_t alignment = fmt_align_t::RIGHT; + std::optional prefix_char; bool uppercase = false; bool alternate_form = false; }; @@ -100,6 +111,11 @@ namespace blt::logging public: explicit fmt_parser_t() = default; + fmt_token_t& peek(const size_t offset) + { + return m_tokens[m_pos + offset]; + } + fmt_token_t& peek() { return m_tokens[m_pos]; @@ -110,6 +126,11 @@ namespace blt::logging return m_pos < m_tokens.size(); } + [[nodiscard]] bool has_next(const size_t offset) const + { + return (m_pos + offset) < m_tokens.size(); + } + [[nodiscard]] fmt_token_t& next() { return m_tokens[m_pos++]; @@ -120,18 +141,27 @@ namespace blt::logging ++m_pos; } + void consume(const size_t amount) + { + m_pos += amount; + } + const fmt_spec_t& parse(std::string_view fmt); private: + static bool is_align_t(fmt_token_type type); + void parse_fmt_field(); void parse_arg_id(); void parse_fmt_spec(); + void parse_fmt_spec_align(); void parse_fmt_spec_sign(); void parse_fmt_spec_form(); void parse_fmt_spec_width(); void parse_fmt_spec_precision(); + void parse_align(); void parse_sign(); void parse_form(); void parse_width(); diff --git a/include/blt/logging/logging.h b/include/blt/logging/logging.h index b696550..91c577f 100644 --- a/include/blt/logging/logging.h +++ b/include/blt/logging/logging.h @@ -19,7 +19,6 @@ #ifndef BLT_LOGGING_LOGGING_H #define BLT_LOGGING_LOGGING_H -#include #include #include #include @@ -37,7 +36,9 @@ namespace blt::logging struct logger_t { - explicit logger_t() = default; + explicit logger_t(std::ostream& stream): m_stream(stream) + { + } template std::string log(std::string fmt, Args&&... args) @@ -51,7 +52,7 @@ namespace blt::logging return to_string(); } - std::string to_string(); + [[nodiscard]] std::string to_string() const; private: template @@ -136,7 +137,7 @@ namespace blt::logging }; } - void setup_stream(const fmt_spec_t& spec); + void setup_stream(const fmt_spec_t& spec) const; void process_strings(); static void handle_type(std::ostream& stream, const fmt_spec_t& spec); @@ -148,7 +149,7 @@ namespace blt::logging std::optional> consume_to_next_fmt(); std::string m_fmt; - std::stringstream m_stream; + std::ostream& m_stream; fmt_parser_t m_parser; // normal sections of string std::vector m_string_sections; @@ -159,7 +160,7 @@ namespace blt::logging size_t m_arg_pos = 0; }; - void print(const std::string& fmt); + void print(const std::string& str); void newline(); @@ -172,12 +173,26 @@ namespace blt::logging print(logger.log(std::move(fmt), std::forward(args)...)); } + template + void print(std::ostream& stream, std::string fmt, Args&&... args) + { + auto& logger = get_global_logger(); + stream << logger.log(std::move(fmt), std::forward(args)...); + } + template void println(std::string fmt, Args&&... args) { print(std::move(fmt), std::forward(args)...); newline(); } + + template + void println(std::ostream& stream, std::string fmt, Args&&... args) + { + print(stream, std::move(fmt), std::forward(args)...); + stream << std::endl; + } } #endif // BLT_LOGGING_LOGGING_H diff --git a/include/blt/meta/is_streamable.h b/include/blt/meta/is_streamable.h index 27756a0..58bea5a 100644 --- a/include/blt/meta/is_streamable.h +++ b/include/blt/meta/is_streamable.h @@ -19,6 +19,8 @@ #ifndef BLT_META_IS_STREAMABLE_H #define BLT_META_IS_STREAMABLE_H +#include + namespace blt::meta { // https://stackoverflow.com/questions/66397071/is-it-possible-to-check-if-overloaded-operator-for-type-or-class-exists diff --git a/src/blt/logging/fmt_tokenizer.cpp b/src/blt/logging/fmt_tokenizer.cpp index dc434ef..5fe4362 100644 --- a/src/blt/logging/fmt_tokenizer.cpp +++ b/src/blt/logging/fmt_tokenizer.cpp @@ -49,6 +49,12 @@ namespace blt::logging return fmt_token_type::SPACE; case '#': return fmt_token_type::POUND; + case '<': + return fmt_token_type::LEFT_CHEVRON; + case '>': + return fmt_token_type::RIGHT_CHEVRON; + case '^': + return fmt_token_type::CARET; default: return fmt_token_type::STRING; } @@ -66,6 +72,9 @@ namespace blt::logging case fmt_token_type::DOT: case fmt_token_type::COLON: case fmt_token_type::POUND: + case fmt_token_type::LEFT_CHEVRON: + case fmt_token_type::RIGHT_CHEVRON: + case fmt_token_type::CARET: return fmt_token_t{base_type, std::string_view{m_fmt.data() + m_pos++, 1}}; default: { @@ -99,6 +108,19 @@ namespace blt::logging return m_spec; } + bool fmt_parser_t::is_align_t(const fmt_token_type type) + { + switch (type) + { + case fmt_token_type::LEFT_CHEVRON: + case fmt_token_type::RIGHT_CHEVRON: + case fmt_token_type::CARET: + return true; + default: + return false; + } + } + void fmt_parser_t::parse_fmt_field() { if (!has_next()) @@ -119,12 +141,7 @@ namespace blt::logging case fmt_token_type::COLON: parse_fmt_spec(); break; - case fmt_token_type::STRING: - case fmt_token_type::SPACE: - case fmt_token_type::DOT: - case fmt_token_type::MINUS: - case fmt_token_type::PLUS: - case fmt_token_type::POUND: + default: { std::stringstream ss; ss << "Expected unknown token '" << static_cast(type) << "' value '" << value << "' when parsing format field"; @@ -155,7 +172,18 @@ namespace blt::logging switch (type) { case fmt_token_type::STRING: + if (has_next(1)) + { + const auto [next_type, next_value] = peek(1); + if (is_align_t(next_type)) + parse_fmt_spec_align(); + } return; + case fmt_token_type::RIGHT_CHEVRON: + case fmt_token_type::LEFT_CHEVRON: + case fmt_token_type::CARET: + parse_fmt_spec_align(); + break; case fmt_token_type::COLON: { std::stringstream ss; @@ -179,6 +207,42 @@ namespace blt::logging } } + void fmt_parser_t::parse_fmt_spec_align() + { + parse_align(); + if (!has_next()) + return; + auto [type, value] = peek(); + switch (type) + { + case fmt_token_type::STRING: + return; + case fmt_token_type::NUMBER: + parse_fmt_spec_width(); + break; + case fmt_token_type::DOT: + parse_fmt_spec_precision(); + break; + case fmt_token_type::SPACE: + case fmt_token_type::MINUS: + case fmt_token_type::PLUS: + parse_fmt_spec_sign(); + break; + case fmt_token_type::POUND: + parse_fmt_spec_form(); + break; + case fmt_token_type::CARET: + case fmt_token_type::COLON: + case fmt_token_type::LEFT_CHEVRON: + case fmt_token_type::RIGHT_CHEVRON: + { + std::stringstream ss; + ss << "(Stage (Begin)) Invalid token type " << static_cast(type) << " value " << value; + throw std::runtime_error(ss.str()); + } + } + } + // handle start of fmt, with sign void fmt_parser_t::parse_fmt_spec_sign() { @@ -194,6 +258,9 @@ namespace blt::logging case fmt_token_type::MINUS: case fmt_token_type::PLUS: case fmt_token_type::COLON: + case fmt_token_type::CARET: + case fmt_token_type::LEFT_CHEVRON: + case fmt_token_type::RIGHT_CHEVRON: { std::stringstream ss; ss << "(Stage (Sign)) Invalid token type " << static_cast(type) << " value " << value; @@ -226,6 +293,9 @@ namespace blt::logging case fmt_token_type::MINUS: case fmt_token_type::PLUS: case fmt_token_type::POUND: + case fmt_token_type::CARET: + case fmt_token_type::LEFT_CHEVRON: + case fmt_token_type::RIGHT_CHEVRON: { std::stringstream ss; ss << "(Stage (Form)) Invalid token type " << static_cast(type) << " value " << value; @@ -257,6 +327,9 @@ namespace blt::logging case fmt_token_type::SPACE: case fmt_token_type::POUND: case fmt_token_type::NUMBER: + case fmt_token_type::CARET: + case fmt_token_type::LEFT_CHEVRON: + case fmt_token_type::RIGHT_CHEVRON: { std::stringstream ss; ss << "(Stage (Width)) Invalid token type " << static_cast(type) << " value " << value; @@ -275,6 +348,36 @@ namespace blt::logging parse_precision(); } + void fmt_parser_t::parse_align() + { + auto [type, value] = next(); + fmt_token_type process_type = type; + if (type == fmt_token_type::STRING) + { + auto [next_type, next_value] = next(); + process_type = next_type; + m_spec.prefix_char = value.front(); + } + switch (process_type) + { + case fmt_token_type::LEFT_CHEVRON: + m_spec.alignment = fmt_align_t::LEFT; + break; + case fmt_token_type::RIGHT_CHEVRON: + m_spec.alignment = fmt_align_t::RIGHT; + break; + case fmt_token_type::CARET: + m_spec.alignment = fmt_align_t::CENTER; + break; + default: + { + std::stringstream ss; + ss << "Invalid align type " << static_cast(process_type) << " value " << value; + throw std::runtime_error(ss.str()); + } + } + } + void fmt_parser_t::parse_sign() { auto [_, value] = next(); @@ -313,8 +416,8 @@ namespace blt::logging void fmt_parser_t::parse_width() { auto [_, value] = next(); - if (value.front() == '0') - m_spec.leading_zeros = true; + if (value.front() == '0' && !m_spec.prefix_char.has_value()) + m_spec.prefix_char = '0'; m_spec.width = std::stoll(std::string(value)); } diff --git a/src/blt/logging/logging.cpp b/src/blt/logging/logging.cpp index c480b7e..8009f0a 100644 --- a/src/blt/logging/logging.cpp +++ b/src/blt/logging/logging.cpp @@ -17,6 +17,7 @@ */ #include #include +#include #include #include #include @@ -25,23 +26,33 @@ namespace blt::logging { struct logging_thread_context_t { - logger_t logger; + std::stringstream stream; + logger_t logger{stream}; }; - std::string logger_t::to_string() + std::string logger_t::to_string() const { - auto str = m_stream.str(); - m_stream.str(""); - m_stream.clear(); - return str; + return dynamic_cast(m_stream).str(); } - void logger_t::setup_stream(const fmt_spec_t& spec) + void logger_t::setup_stream(const fmt_spec_t& spec) const { - if (spec.leading_zeros) - m_stream << std::setfill('0'); + if (spec.prefix_char) + m_stream << std::setfill(*spec.prefix_char); else m_stream << std::setfill(' '); + switch (spec.alignment) + { + case fmt_align_t::LEFT: + m_stream << std::left; + break; + case fmt_align_t::CENTER: + // TODO? + break; + case fmt_align_t::RIGHT: + m_stream << std::right; + break; + } if (spec.width > 0) m_stream << std::setw(static_cast(spec.width)); else @@ -52,6 +63,8 @@ namespace blt::logging m_stream << std::setprecision(16); if (spec.alternate_form) m_stream << std::showbase; + else + m_stream << std::noshowbase; if (spec.uppercase) m_stream << std::uppercase; else @@ -129,7 +142,8 @@ namespace blt::logging m_fmt = std::move(fmt); m_last_fmt_pos = 0; m_arg_pos = 0; - m_stream.str(""); + auto& ss = dynamic_cast(m_stream); + ss.str(""); m_stream.clear(); m_string_sections.clear(); m_fmt_specs.clear(); @@ -171,9 +185,9 @@ namespace blt::logging return context.logger; } - void print(const std::string& fmt) + void print(const std::string& str) { - std::cout << fmt; + std::cout << str; } void newline() diff --git a/tests/logger_tests.cpp b/tests/logger_tests.cpp index c4d7bc5..64cccb2 100644 --- a/tests/logger_tests.cpp +++ b/tests/logger_tests.cpp @@ -15,45 +15,112 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#include +#include #include +#include #include struct some_silly_type_t { }; +auto expected_str = std::string(R"(This is a println! +This is a println with args '42' +This is a println with multiple args '42' '32.342311859130859375' 'Hello World!' +This is a 'Well so am I except cooler :3' fmt string with positionals 'I am a string!' +This is a println with a sign +4120 +This is a println with a sign -4120 +This is a println with a space 4120 +This is a println with a space -4120 +This is a println with a minus 4120 +This is a println with a minus -4120 +This is a println with a with 4120 +This is a println with a with leading zeros 0000004120 +This is a println with a precision 42.2323423490 +This is a println with hex 109a +This is a println with hex with leading 0x109a +This is a println with binary 0b00110010000110100101011000000000 +This is a println with binary with space 0b10110010 00011010 01010110 00000000 +This is a println with binary with space 10100010 00000000 00000000 00000000 +This is a println with octal 015015 +This is a println with hexfloat 0x1.926e978d4fdf4p+8 +This is a println with exponent 4.4320902431999996e+07 +This is a println with exponent 9.5324342340423400e+15 +This is a println with general 953243.49 +This is a println with general 9.532433240234033e+17 +This is a println with a char B +This is a println with type some_silly_type_t +This is a println with boolean true +This is a println with boolean as int 0 +This is a println with boolean as hex 0x1 +This is a println with boolean as octal 1 +This is a println with alignment left 64 end value +This is a println with alignment right 46 end value +This is a println with alignment left (fill) 46******** end value +This is a println with alignment right (fill) ********46 end value +)"); + +std::pair compare_strings(const std::string& s1, const std::string& s2) +{ + if (s1.size() != s2.size()) + return {false, "Strings size do not match '" + std::to_string(s1.size()) + "' vs '" + std::to_string(s2.size()) + "'"}; + size_t index = 0; + for (; index < s1.size(); ++index) + { + if (s1[index] != s2[index]) + { + std::stringstream ss; + const auto i1 = std::max(static_cast(index) - 32, 0l); + const auto l1 = std::min(static_cast(s1.size()) - i1, 65l); + ss << "Strings differ at index " << index << "!\n"; + ss << "'" << s1.substr(i1, l1) << "' vs '" << s2.substr(i1, l1) << "'" << std::endl; + return {false, ss.str()}; + } + } + return {true, ""}; +} + int main() { - blt::logging::println("This is a println!"); - blt::logging::println("This is a println with args '{}'", 42); - blt::logging::println("This is a println with multiple args '{}' '{:.100}' '{}'", 42, 32.34231233f, "Hello World!"); - blt::logging::println("This is a '{1}' fmt string with positionals '{0}'", "I am a string!", "Well so am I except cooler :3"); - blt::logging::println("This is a println with a sign {:+}", 4120); - blt::logging::println("This is a println with a sign {:+}", -4120); - blt::logging::println("This is a println with a space {: }", 4120); - blt::logging::println("This is a println with a space {: }", -4120); - blt::logging::println("This is a println with a minus {:-}", 4120); - blt::logging::println("This is a println with a minus {:-}", -4120); - blt::logging::println("This is a println with a with {:10}", 4120); - blt::logging::println("This is a println with a with leading zeros {:010}", 4120); - blt::logging::println("This is a println with a precision {:.10f}", 42.232342349); - blt::logging::println("This is a println with hex {:.10x}", 4250); - blt::logging::println("This is a println with hex with leading {:#.10x}", 4250); - blt::logging::println("This is a println with binary {:#b}", 6969420); - blt::logging::println("This is a println with binary with space {: #b}", 6969421); - blt::logging::println("This is a println with binary with space {: b}", 69); - blt::logging::println("This is a println with octal {:#o}", 6669); - blt::logging::println("This is a println with hexfloat {:a}", 402.4320); - blt::logging::println("This is a println with exponent {:e}", 44320902.4320); - blt::logging::println("This is a println with exponent {:e}", 9532434234042340.0); - blt::logging::println("This is a println with general {:g}", 953243.49); - blt::logging::println("This is a println with general {:g}", 953243324023403240.49); - blt::logging::println("This is a println with a char {:c}", 66); - blt::logging::println("This is a println with type {:t}", some_silly_type_t{}); - blt::logging::println("This is a println with boolean {}", true); - blt::logging::println("This is a println with boolean as int {:d}", false); - blt::logging::println("This is a println with boolean as hex {:#x}", true); - blt::logging::println("This is a println with boolean as octal {:o}", true); - blt::logging::println("This is a println with boolean as test {:h}", true); + std::stringstream ss; + blt::logging::println(ss, "This is a println!"); + blt::logging::println(ss, "This is a println with args '{}'", 42); + blt::logging::println(ss, "This is a println with multiple args '{}' '{:.100}' '{}'", 42, 32.34231233f, "Hello World!"); + blt::logging::println(ss, "This is a '{1}' fmt string with positionals '{0}'", "I am a string!", "Well so am I except cooler :3"); + blt::logging::println(ss, "This is a println with a sign {:+}", 4120); + blt::logging::println(ss, "This is a println with a sign {:+}", -4120); + blt::logging::println(ss, "This is a println with a space {: }", 4120); + blt::logging::println(ss, "This is a println with a space {: }", -4120); + blt::logging::println(ss, "This is a println with a minus {:-}", 4120); + blt::logging::println(ss, "This is a println with a minus {:-}", -4120); + blt::logging::println(ss, "This is a println with a with {:10}", 4120); + blt::logging::println(ss, "This is a println with a with leading zeros {:010}", 4120); + blt::logging::println(ss, "This is a println with a precision {:.10f}", 42.232342349); + blt::logging::println(ss, "This is a println with hex {:.10x}", 4250); + blt::logging::println(ss, "This is a println with hex with leading {:#.10x}", 4250); + blt::logging::println(ss, "This is a println with binary {:#b}", 6969420); + blt::logging::println(ss, "This is a println with binary with space {: #b}", 6969421); + blt::logging::println(ss, "This is a println with binary with space {: b}", 69); + blt::logging::println(ss, "This is a println with octal {:#o}", 6669); + blt::logging::println(ss, "This is a println with hexfloat {:a}", 402.4320); + blt::logging::println(ss, "This is a println with exponent {:e}", 44320902.4320); + blt::logging::println(ss, "This is a println with exponent {:e}", 9532434234042340.0); + blt::logging::println(ss, "This is a println with general {:g}", 953243.49); + blt::logging::println(ss, "This is a println with general {:g}", 953243324023403240.49); + blt::logging::println(ss, "This is a println with a char {:c}", 66); + blt::logging::println(ss, "This is a println with type {:t}", some_silly_type_t{}); + blt::logging::println(ss, "This is a println with boolean {}", true); + blt::logging::println(ss, "This is a println with boolean as int {:d}", false); + blt::logging::println(ss, "This is a println with boolean as hex {:#x}", true); + blt::logging::println(ss, "This is a println with boolean as octal {:o}", true); + blt::logging::println(ss, "This is a println with alignment left {:<10} end value", 64); + blt::logging::println(ss, "This is a println with alignment right {:>10} end value", 46); + blt::logging::println(ss, "This is a println with alignment left (fill) {:*<10} end value", 46); + blt::logging::println(ss, "This is a println with alignment right (fill) {:*>10} end value", 46); + blt::logging::print(ss.str()); + auto [passed, error_msg] = compare_strings(expected_str, ss.str()); + BLT_ASSERT_MSG(passed && "Logger logged string doesn't match precomputed expected string!", error_msg.c_str()); + // blt::logging::println("This is println {}\twith a std::endl in the middle of it"); }