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");
+}