From 0490f50e3cf90f8e001414c9be7ff83827f123ab Mon Sep 17 00:00:00 2001 From: Brett Date: Mon, 3 Mar 2025 01:52:20 -0500 Subject: [PATCH] tokenizer, basic logging works --- CMakeLists.txt | 2 +- include/blt/logging/fmt_tokenizer.h | 64 +++++++++++++++++++++++ include/blt/logging/logging.h | 36 ++++++++++--- src/blt/logging/fmt_tokenizer.cpp | 81 +++++++++++++++++++++++++++++ src/blt/logging/logging.cpp | 57 ++++++++++++++++++-- tests/logger_tests.cpp | 8 ++- 6 files changed, 237 insertions(+), 11 deletions(-) create mode 100644 include/blt/logging/fmt_tokenizer.h create mode 100644 src/blt/logging/fmt_tokenizer.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0b06a48..171443e 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.0.0) +set(BLT_VERSION 5.1.0) set(BLT_TARGET BLT) diff --git a/include/blt/logging/fmt_tokenizer.h b/include/blt/logging/fmt_tokenizer.h new file mode 100644 index 0000000..ca0d05d --- /dev/null +++ b/include/blt/logging/fmt_tokenizer.h @@ -0,0 +1,64 @@ +#pragma once +/* + * Copyright (C) 2024 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BLT_LOGGING_FMT_TOKENIZER_H +#define BLT_LOGGING_FMT_TOKENIZER_H + +#include +#include +#include +#include + +namespace blt::logging +{ + enum class fmt_token_type : u8 + { + STRING, + NUMBER, + SPACE, + COLON, + DOT, + MINUS, + PLUS + }; + + struct fmt_token_t + { + fmt_token_type type; + std::string_view value; + }; + + class fmt_tokenizer_t + { + public: + explicit fmt_tokenizer_t(const std::string_view fmt): m_fmt(fmt) + {} + + static fmt_token_type get_type(char c); + + std::optional next(); + + std::vector tokenize(); + + private: + size_t pos = 0; + std::string_view m_fmt; + }; +} + +#endif //BLT_LOGGING_FMT_TOKENIZER_H diff --git a/include/blt/logging/logging.h b/include/blt/logging/logging.h index 48648c5..1cd41ee 100644 --- a/include/blt/logging/logging.h +++ b/include/blt/logging/logging.h @@ -19,11 +19,9 @@ #ifndef BLT_LOGGING_LOGGING_H #define BLT_LOGGING_LOGGING_H -#include -#include #include #include -#include +#include #include namespace blt::logging @@ -33,30 +31,56 @@ namespace blt::logging explicit logger_t() = default; template - void print_value(T&& t) + std::string print_value(T&& t) { static_assert(meta::is_streamable_v, "T must be streamable in order to work with blt::logging!"); + m_stream.str(""); + m_stream.clear(); m_stream << std::forward(t); + return m_stream.str(); } template std::string log(std::string fmt, Args&&... args) { compile(std::move(fmt)); - ((consume_until_fmt(), print_value(std::forward(args))), ...); + m_args_to_str.clear(); + m_args_to_str.resize(sizeof...(Args)); + insert(std::make_integer_sequence{}, std::forward(args)...); + finish(); return to_string(); } std::string to_string(); private: + template + void insert(std::integer_sequence, Args&&... args) + { + ((handle_insert(std::forward(args))), ...); + } + + template + void handle_insert(T&& t) + { + m_args_to_str[index] = print_value(std::forward(t)); + } + + void handle_fmt(std::string_view fmt); + + const std::string& get(size_t index); + void compile(std::string fmt); - void consume_until_fmt(); + bool consume_until_fmt(); + + void finish(); std::string m_fmt; std::stringstream m_stream; + std::vector m_args_to_str; size_t m_last_fmt_pos = 0; + size_t m_arg_pos = 0; }; void print(const std::string& fmt); diff --git a/src/blt/logging/fmt_tokenizer.cpp b/src/blt/logging/fmt_tokenizer.cpp new file mode 100644 index 0000000..3784ece --- /dev/null +++ b/src/blt/logging/fmt_tokenizer.cpp @@ -0,0 +1,81 @@ +/* + * + * Copyright (C) 2025 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace blt::logging +{ + fmt_token_type fmt_tokenizer_t::get_type(const char c) + { + switch (c) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return fmt_token_type::NUMBER; + case '+': + return fmt_token_type::PLUS; + case '-': + return fmt_token_type::MINUS; + case '.': + return fmt_token_type::DOT; + case ':': + return fmt_token_type::COLON; + case ' ': + return fmt_token_type::SPACE; + default: + return fmt_token_type::STRING; + } + } + + std::optional fmt_tokenizer_t::next() + { + if (pos >= m_fmt.size()) + return {}; + switch (const auto base_type = get_type(m_fmt[pos])) + { + case fmt_token_type::SPACE: + case fmt_token_type::PLUS: + case fmt_token_type::MINUS: + case fmt_token_type::DOT: + case fmt_token_type::COLON: + return fmt_token_t{base_type, std::string_view{m_fmt.data() + pos++, 1}}; + default: + { + const auto begin = pos; + for (; pos < m_fmt.size() && get_type(m_fmt[pos]) == base_type; ++pos) + {} + return fmt_token_t{base_type, std::string_view{m_fmt.data() + begin, pos - begin}}; + } + } + } + + std::vector fmt_tokenizer_t::tokenize() + { + std::vector tokens; + while (auto token = next()) + tokens.push_back(*token); + return tokens; + } +} diff --git a/src/blt/logging/logging.cpp b/src/blt/logging/logging.cpp index f16eac4..4e9309b 100644 --- a/src/blt/logging/logging.cpp +++ b/src/blt/logging/logging.cpp @@ -15,6 +15,8 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#include +#include #include #include @@ -33,18 +35,67 @@ namespace blt::logging return str; } + void logger_t::handle_fmt(const std::string_view fmt) + { + std::cout << fmt << std::endl; + } + + const std::string& logger_t::get(const size_t index) + { + if (index >= m_args_to_str.size()) + { + std::cerr << "Insufficient number of arguments provided to format string '" << m_fmt << "' got "; + for (const auto& [i, arg] : enumerate(std::as_const(m_args_to_str))) + { + std::cerr << '\'' << arg << "'"; + if (i != m_args_to_str.size() - 1) + std::cerr << " "; + } + std::exit(EXIT_FAILURE); + } + return m_args_to_str[index]; + } + void logger_t::compile(std::string fmt) { m_fmt = std::move(fmt); m_last_fmt_pos = 0; + m_arg_pos = 0; } - void logger_t::consume_until_fmt() + bool logger_t::consume_until_fmt() { const auto begin = m_fmt.find('{', m_last_fmt_pos); + if (begin == std::string::npos) + return false; const auto end = m_fmt.find('}', begin); - m_stream << std::string_view(m_fmt.data() + static_cast(m_last_fmt_pos), begin - m_last_fmt_pos); - m_last_fmt_pos = end; + 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);\ + if (end - begin > 1) + handle_fmt(std::string_view(m_fmt.data() + static_cast(begin + 1), end - begin - 1)); + else + { + // no arguments, must consume from args + m_stream << get(m_arg_pos++); + } + m_last_fmt_pos = end + 1; + return true; + } + + void logger_t::finish() + { + m_stream.str(""); + m_stream.clear(); + + while (consume_until_fmt()) + {} + + 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(); } logger_t& get_global_logger() diff --git a/tests/logger_tests.cpp b/tests/logger_tests.cpp index 7e0e946..e0de5c7 100644 --- a/tests/logger_tests.cpp +++ b/tests/logger_tests.cpp @@ -16,10 +16,16 @@ * along with this program. If not, see . */ #include +#include int main() { + + using endl_t = decltype(static_cast(std::endl)); + // 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!"); -} \ No newline at end of file + 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 println {}\twith a std::endl in the middle of it"); +}