From b2c3820ed0d6975c722a163b66497250a3b684c1 Mon Sep 17 00:00:00 2001 From: Brett Date: Tue, 4 Mar 2025 00:44:48 -0500 Subject: [PATCH] basic logger works --- CMakeLists.txt | 2 +- include/blt/logging/logging.h | 155 ++++++++++++------- libraries/parallel-hashmap | 2 +- src/blt/logging/fmt_tokenizer.cpp | 79 +++++----- src/blt/logging/logging.cpp | 241 +++++++++++++++++------------- tests/logger_tests.cpp | 10 +- 6 files changed, 280 insertions(+), 209 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7d95e2c..d876b96 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.2) +set(BLT_VERSION 5.1.3) set(BLT_TARGET BLT) diff --git a/include/blt/logging/logging.h b/include/blt/logging/logging.h index cf98092..c3cf7b2 100644 --- a/include/blt/logging/logging.h +++ b/include/blt/logging/logging.h @@ -21,12 +21,18 @@ #include #include +#include #include -#include +#include +#include #include namespace blt::logging { + namespace detail + { + } + struct logger_t { explicit logger_t() = default; @@ -36,83 +42,116 @@ namespace blt::logging { compile(std::move(fmt)); auto sequence = std::make_integer_sequence{}; - while (auto pair = consume_until_fmt()) - { - auto [begin, end] = *pair; - if (end - begin > 0) - { - auto format_data = handle_fmt(m_fmt.substr(begin + 1, begin - end - 1)); - auto [arg_pos, fmt_type] = format_data; - if (arg_pos == -1) - arg_pos = static_cast(m_arg_pos++); - if (fmt_type) - { - if (fmt_type == fmt_type_t::GENERAL) - { - apply_func([this](auto&& value) - { - if (static_cast(value) > 0xFFFFFFFFFul) - exponential(); - else - fixed(); - m_stream << std::forward(value); - }, arg_pos, sequence, std::forward(args)...); - } else if (fmt_type == fmt_type_t::CHAR) - { - - } else if (fmt_type == fmt_type_t::BINARY) - { - - } - } - else - { - apply_func([this](auto&& value) - { - m_stream << std::forward(value); - }, arg_pos, sequence, std::forward(args)...); - } - } - else - apply_func([this](auto&& value) - { - m_stream << std::forward(value); - }, m_arg_pos++, sequence, std::forward(args)...); - } - finish(); + m_arg_print_funcs.clear(); + m_arg_print_funcs.resize(sizeof...(Args)); + create_conv_funcs(sequence, std::forward(args)...); + process_strings(); return to_string(); } std::string to_string(); private: - template - void apply_func(const Func& func, const size_t arg, std::integer_sequence, Args&&... args) + template + void create_conv_funcs(std::integer_sequence, Args&&... args) { - ((handle_func(func, arg, std::forward(args))), ...); + ((handle_func(std::forward(args))), ...); } - template - void handle_func(const Func& func, const size_t arg, T&& t) + template + void handle_func(const T& t) { - if (index == arg) - func(std::forward(t)); + m_arg_print_funcs[index] = [&t, this](std::ostream& stream, const fmt_spec_t& type) + { + switch (type.sign) + { + case fmt_sign_t::SPACE: + if constexpr (std::is_arithmetic_v) + { + if (t >= 0) + stream << ' '; + } + break; + case fmt_sign_t::PLUS: + if constexpr (std::is_arithmetic_v) + { + if (t >= 0) + stream << '+'; + } + break; + case fmt_sign_t::MINUS: + break; + } + switch (type.type) + { + case fmt_type_t::BINARY: + { + if constexpr (std::is_trivially_copyable_v) + { + char buffer[sizeof(T)]; + std::memcpy(buffer, &t, sizeof(T)); + stream << '0' << (type.uppercase ? 'B' : 'b'); + for (size_t i = 0; i < sizeof(T); ++i) + { + for (size_t j = 0; j < 8; ++j) + stream << ((buffer[i] & (1 << j)) ? '1' : '0'); + if (type.sign == fmt_sign_t::SPACE && i != sizeof(T) - 1) + stream << ' '; + } + } else + { + stream << t; + } + break; + } + case fmt_type_t::CHAR: + if constexpr (std::is_arithmetic_v || std::is_convertible_v) + { + stream << static_cast(t); + } else + { + stream << t; + } + break; + case fmt_type_t::GENERAL: + if constexpr (std::is_arithmetic_v) + { + if (static_cast(t) > 10e12) + exponential(stream); + else + fixed(stream); + stream << t; + } else + { + stream << t; + } + break; + default: + handle_type(stream, type.type); + stream << t; + } + }; } - [[nodiscard]] std::pair> handle_fmt(std::string_view fmt); + void setup_stream(const fmt_spec_t& spec); + void process_strings(); + static void handle_type(std::ostream& stream, fmt_type_t type); - void exponential(); - void fixed(); + static void exponential(std::ostream& stream); + static void fixed(std::ostream& stream); void compile(std::string fmt); - std::optional> consume_until_fmt(); - - void finish(); + std::optional> consume_to_next_fmt(); std::string m_fmt; std::stringstream m_stream; fmt_parser_t m_parser; + // normal sections of string + std::vector m_string_sections; + // processed format specs + std::vector m_fmt_specs; + std::vector> m_arg_print_funcs; size_t m_last_fmt_pos = 0; size_t m_arg_pos = 0; }; diff --git a/libraries/parallel-hashmap b/libraries/parallel-hashmap index 7ef2e73..93201da 160000 --- a/libraries/parallel-hashmap +++ b/libraries/parallel-hashmap @@ -1 +1 @@ -Subproject commit 7ef2e733416953b222851f9a360d7fc72d068ee5 +Subproject commit 93201da2ba5a6aba0a6e57ada64973555629b3e3 diff --git a/src/blt/logging/fmt_tokenizer.cpp b/src/blt/logging/fmt_tokenizer.cpp index b2bb522..bfb6426 100644 --- a/src/blt/logging/fmt_tokenizer.cpp +++ b/src/blt/logging/fmt_tokenizer.cpp @@ -16,6 +16,7 @@ * along with this program. If not, see . */ #include +#include #include namespace blt::logging @@ -97,10 +98,7 @@ namespace blt::logging void fmt_parser_t::parse_fmt_field() { if (!has_next()) - { - std::cerr << "Expected token when parsing format field" << std::endl; - std::exit(EXIT_FAILURE); - } + throw std::runtime_error("Expected token when parsing format field"); const auto [type, value] = peek(); if (type == fmt_token_type::COLON) { @@ -118,16 +116,14 @@ namespace blt::logging parse_fmt_spec_stage_1(); } else - { - std::cerr << "Expected ':' when parsing format field after arg id!" << std::endl; - std::exit(EXIT_FAILURE); - } + throw std::runtime_error("Expected ':' when parsing format field after arg id!"); } } else { - std::cerr << "Expected unknown token '" << static_cast(type) << "' value '" << value << "' when parsing format field" << std::endl; - std::exit(EXIT_FAILURE); + std::stringstream ss; + ss << "Expected unknown token '" << static_cast(type) << "' value '" << value << "' when parsing format field"; + throw std::runtime_error(ss.str()); } if (has_next()) parse_type(); @@ -136,15 +132,13 @@ namespace blt::logging void fmt_parser_t::parse_arg_id() { if (!has_next()) - { - std::cerr << "Missing token when parsing arg id" << std::endl; - std::exit(EXIT_FAILURE); - } + throw std::runtime_error("Missing token when parsing arg id"); const auto [type, value] = next(); if (type != fmt_token_type::NUMBER) { - std::cerr << "Expected number when parsing arg id, unexpected value '" << value << '\'' << std::endl; - std::exit(EXIT_FAILURE); + std::stringstream ss; + ss << "Expected number when parsing arg id, unexpected value '" << value << '\''; + throw std::runtime_error(ss.str()); } m_spec.arg_id = std::stoll(std::string(value)); } @@ -159,8 +153,11 @@ namespace blt::logging { case fmt_token_type::STRING: case fmt_token_type::COLON: - std::cerr << "(Stage 1) Invalid token type " << static_cast(type) << " value " << value << std::endl; - std::exit(EXIT_FAILURE); + { + std::stringstream ss; + ss << "(Stage 1) Invalid token type " << static_cast(type) << " value " << value; + throw std::runtime_error(ss.str()); + } case fmt_token_type::NUMBER: parse_width(); parse_fmt_spec_stage_3(); @@ -191,8 +188,11 @@ namespace blt::logging case fmt_token_type::MINUS: case fmt_token_type::PLUS: case fmt_token_type::SPACE: - std::cerr << "(Stage 2) Invalid token type " << static_cast(type) << " value " << value << std::endl; - std::exit(EXIT_FAILURE); + { + std::stringstream ss; + ss << "(Stage 2) Invalid token type " << static_cast(type) << " value " << value; + throw std::runtime_error(ss.str()); + } case fmt_token_type::NUMBER: parse_width(); parse_fmt_spec_stage_3(); @@ -217,8 +217,11 @@ namespace blt::logging case fmt_token_type::PLUS: case fmt_token_type::SPACE: case fmt_token_type::NUMBER: - std::cerr << "(Stage 3) Invalid token type " << static_cast(type) << " value " << value << std::endl; - std::exit(EXIT_FAILURE); + { + std::stringstream ss; + ss << "(Stage 3) Invalid token type " << static_cast(type) << " value " << value; + throw std::runtime_error(ss.str()); + } case fmt_token_type::DOT: consume(); parse_precision(); @@ -231,8 +234,9 @@ namespace blt::logging auto [_, value] = next(); if (value.size() > 1) { - std::cerr << "Sign contains more than one character, we are not sure how to interpret this. Value '" << value << "'\n"; - std::exit(EXIT_FAILURE); + std::stringstream ss; + ss << "Sign contains more than one character, we are not sure how to interpret this. Value '" << value << "'"; + throw std::runtime_error(ss.str()); } switch (value[0]) { @@ -246,8 +250,11 @@ namespace blt::logging m_spec.sign = fmt_sign_t::SPACE; break; default: - std::cerr << "Invalid sign " << value[0] << std::endl; - std::exit(EXIT_FAILURE); + { + std::stringstream ss; + ss << "Invalid sign " << value[0]; + throw std::runtime_error(ss.str()); + } } } @@ -262,10 +269,7 @@ namespace blt::logging void fmt_parser_t::parse_precision() { if (!has_next()) - { - std::cerr << "Missing token when parsing precision" << std::endl; - std::exit(EXIT_FAILURE); - } + throw std::runtime_error("Missing token when parsing precision"); auto [_, value] = next(); m_spec.precision = std::stoll(std::string(value)); } @@ -273,15 +277,13 @@ namespace blt::logging void fmt_parser_t::parse_type() { if (!has_next()) - { - std::cerr << "Missing token when parsing type" << std::endl; - std::exit(EXIT_FAILURE); - } + throw std::runtime_error("Missing token when parsing type"); auto [_, value] = next(); if (value.size() != 1) { - std::cerr << "Type contains more than one character, we are not sure how to interpret this value '" << value << "'\n"; - std::exit(EXIT_FAILURE); + std::stringstream ss; + ss << "Type contains more than one character, we are not sure how to interpret this value '" << value << "'"; + throw std::runtime_error(ss.str()); } m_spec.uppercase = std::isupper(value.front()); switch (value.front()) @@ -320,8 +322,9 @@ namespace blt::logging m_spec.type = fmt_type_t::GENERAL; break; default: - std::cerr << "Invalid type " << value << std::endl; - std::exit(EXIT_FAILURE); + std::stringstream ss; + ss << "Invalid type " << value; + throw std::runtime_error(ss.str()); } } } diff --git a/src/blt/logging/logging.cpp b/src/blt/logging/logging.cpp index 971269f..9a8bb1b 100644 --- a/src/blt/logging/logging.cpp +++ b/src/blt/logging/logging.cpp @@ -23,122 +23,147 @@ namespace blt::logging { - struct logging_thread_context_t - { - logger_t logger; - }; + struct logging_thread_context_t + { + logger_t logger; + }; - std::string logger_t::to_string() - { - auto str = m_stream.str(); - m_stream.str(""); - m_stream.clear(); - return str; - } + std::string logger_t::to_string() + { + auto str = m_stream.str(); + m_stream.str(""); + m_stream.clear(); + return str; + } - std::pair> logger_t::handle_fmt(const std::string_view fmt) - { - const auto spec = m_parser.parse(fmt); - if (spec.leading_zeros) - m_stream << std::setfill('0'); - else - m_stream << std::setfill(' '); - if (spec.width > 0) - m_stream << std::setw(static_cast(spec.width)); - else - m_stream << std::setw(0); - if (spec.precision > 0) - m_stream << std::setprecision(static_cast(spec.precision)); - else - m_stream << std::setprecision(2); - if (spec.uppercase) - m_stream << std::uppercase; - else - m_stream << std::nouppercase; - std::optional type; - switch (spec.type) - { - case fmt_type_t::BINARY: - case fmt_type_t::CHAR: - case fmt_type_t::GENERAL: - type = spec.type; - break; - case fmt_type_t::DECIMAL: - m_stream << std::dec; - break; - case fmt_type_t::OCTAL: - m_stream << std::oct; - break; - case fmt_type_t::HEX: - m_stream << std::hex; - break; - case fmt_type_t::HEX_FLOAT: - m_stream << std::hexfloat; - break; - case fmt_type_t::EXPONENT: - m_stream << std::scientific; - break; - case fmt_type_t::FIXED_POINT: - m_stream << std::fixed; - break; - } - return {spec.arg_id, type}; - } + void logger_t::setup_stream(const fmt_spec_t& spec) + { + if (spec.leading_zeros) + m_stream << std::setfill('0'); + else + m_stream << std::setfill(' '); + if (spec.width > 0) + m_stream << std::setw(static_cast(spec.width)); + else + m_stream << std::setw(0); + if (spec.precision > 0) + m_stream << std::setprecision(static_cast(spec.precision)); + else + m_stream << std::setprecision(static_cast(std::cout.precision())); + if (spec.uppercase) + m_stream << std::uppercase; + else + m_stream << std::nouppercase; + } - void logger_t::exponential() - { - m_stream << std::scientific; - } + void logger_t::process_strings() + { + auto spec_it = m_fmt_specs.begin(); + auto str_it = m_string_sections.begin(); + for (; spec_it != m_fmt_specs.end(); ++spec_it, ++str_it) + { + m_stream << *str_it; + auto arg_pos = spec_it->arg_id; + if (arg_pos == -1) + arg_pos = static_cast(m_arg_pos++); - void logger_t::fixed() - { - m_stream << std::fixed; - } + setup_stream(*spec_it); + m_arg_print_funcs[arg_pos](m_stream, *spec_it); + } + m_stream << *str_it; + } - void logger_t::compile(std::string fmt) - { - m_fmt = std::move(fmt); - m_last_fmt_pos = 0; - m_arg_pos = 0; - m_stream.str(""); - m_stream.clear(); - } + void logger_t::handle_type(std::ostream& stream, const fmt_type_t type) + { + switch (type) + { + case fmt_type_t::DECIMAL: + stream << std::dec; + break; + case fmt_type_t::OCTAL: + stream << std::oct; + break; + case fmt_type_t::HEX: + stream << std::hex; + break; + case fmt_type_t::HEX_FLOAT: + stream << std::hexfloat; + break; + case fmt_type_t::EXPONENT: + stream << std::scientific; + break; + case fmt_type_t::FIXED_POINT: + stream << std::fixed; + break; + default: + break; + } + } - std::optional> logger_t::consume_until_fmt() - { - const auto begin = m_fmt.find('{', m_last_fmt_pos); - if (begin == std::string::npos) - return {}; - const auto end = m_fmt.find('}', begin); - if (end == std::string::npos) - { - std::cerr << "Invalid format string, missing closing '}' near " << m_fmt.substr(std::min(static_cast(begin) - 5, 0l)) << std::endl; - std::exit(EXIT_FAILURE); - } - m_stream << std::string_view(m_fmt.data() + static_cast(m_last_fmt_pos), begin - m_last_fmt_pos);\ - m_last_fmt_pos = end + 1; - return std::pair{begin, end}; - } + void logger_t::exponential(std::ostream& stream) + { + stream << std::scientific; + } - void logger_t::finish() - { - m_stream << std::string_view(m_fmt.data() + static_cast(m_last_fmt_pos), m_fmt.size() - m_last_fmt_pos); - m_last_fmt_pos = m_fmt.size(); - } + void logger_t::fixed(std::ostream& stream) + { + stream << std::fixed; + } - logger_t& get_global_logger() - { - thread_local logging_thread_context_t context; - return context.logger; - } + void logger_t::compile(std::string fmt) + { + m_fmt = std::move(fmt); + m_last_fmt_pos = 0; + m_arg_pos = 0; + m_stream.str(""); + m_stream.clear(); + m_string_sections.clear(); + m_fmt_specs.clear(); + ptrdiff_t last_pos = 0; + while (auto pair = consume_to_next_fmt()) + { + const auto [begin, end] = *pair; + m_string_sections.emplace_back(m_fmt.data() + last_pos, begin - last_pos); + if (end - begin > 1) + m_fmt_specs.push_back(m_parser.parse(std::string_view{m_fmt.data() + static_cast(begin) + 1, end - begin - 1})); + else + m_fmt_specs.emplace_back(); + last_pos = static_cast(end) + 1; + } + m_string_sections.emplace_back(m_fmt.data() + last_pos, m_fmt.size() - last_pos); + m_last_fmt_pos = 0; + } - void print(const std::string& fmt) - { - std::cout << fmt; - } + std::optional> logger_t::consume_to_next_fmt() + { + const auto begin = m_fmt.find('{', m_last_fmt_pos); + if (begin == std::string::npos) + return {}; + const auto next_begin = m_fmt.find('{', begin + 1); + const auto end = m_fmt.find('}', begin); + if (end == std::string::npos || (next_begin != std::string::npos && next_begin < end)) + { + std::stringstream ss; + ss << "Invalid format string, missing closing '}' near " << m_fmt.substr(std::min(static_cast(begin) - 5, 0l)); + throw std::runtime_error(ss.str()); + } + m_last_fmt_pos = end + 1; + return std::pair{begin, end}; + } - void newline() - { - std::cout << std::endl; - } + logger_t& get_global_logger() + { + thread_local logging_thread_context_t context; + return context.logger; + } + + void print(const std::string& fmt) + { + std::cout << fmt; + } + + void newline() + { + std::cout << std::endl; + } } diff --git a/tests/logger_tests.cpp b/tests/logger_tests.cpp index 77e8046..a7f4f62 100644 --- a/tests/logger_tests.cpp +++ b/tests/logger_tests.cpp @@ -25,12 +25,16 @@ int main() // blt::logging::println("{} | {} | {} | {}", blt::type_string()); 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 '{}' '{}' '{}'", 42, 32.34231233, "Hello World!"); + 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 with {:3}", 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 {:.3}", 42.232342349); + blt::logging::println("This is a println with a precision {:.10f}", 42.232342349); // blt::logging::println("This is println {}\twith a std::endl in the middle of it"); }