basic logger works

main
Brett 2025-03-04 00:44:48 -05:00
parent 74878d6b43
commit b2c3820ed0
6 changed files with 280 additions and 209 deletions

View File

@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.20) cmake_minimum_required(VERSION 3.20)
include(cmake/color.cmake) include(cmake/color.cmake)
set(BLT_VERSION 5.1.2) set(BLT_VERSION 5.1.3)
set(BLT_TARGET BLT) set(BLT_TARGET BLT)

View File

@ -21,12 +21,18 @@
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <string_view>
#include <vector> #include <vector>
#include <blt/meta/meta.h> #include <cstring>
#include <functional>
#include <blt/logging/fmt_tokenizer.h> #include <blt/logging/fmt_tokenizer.h>
namespace blt::logging namespace blt::logging
{ {
namespace detail
{
}
struct logger_t struct logger_t
{ {
explicit logger_t() = default; explicit logger_t() = default;
@ -36,83 +42,116 @@ namespace blt::logging
{ {
compile(std::move(fmt)); compile(std::move(fmt));
auto sequence = std::make_integer_sequence<size_t, sizeof...(Args)>{}; auto sequence = std::make_integer_sequence<size_t, sizeof...(Args)>{};
while (auto pair = consume_until_fmt()) m_arg_print_funcs.clear();
{ m_arg_print_funcs.resize(sizeof...(Args));
auto [begin, end] = *pair; create_conv_funcs(sequence, std::forward<Args>(args)...);
if (end - begin > 0) process_strings();
{
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<i64>(m_arg_pos++);
if (fmt_type)
{
if (fmt_type == fmt_type_t::GENERAL)
{
apply_func([this](auto&& value)
{
if (static_cast<u64>(value) > 0xFFFFFFFFFul)
exponential();
else
fixed();
m_stream << std::forward<decltype(value)>(value);
}, arg_pos, sequence, std::forward<Args>(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<decltype(value)>(value);
}, arg_pos, sequence, std::forward<Args>(args)...);
}
}
else
apply_func([this](auto&& value)
{
m_stream << std::forward<decltype(value)>(value);
}, m_arg_pos++, sequence, std::forward<Args>(args)...);
}
finish();
return to_string(); return to_string();
} }
std::string to_string(); std::string to_string();
private: private:
template <typename Func, typename... Args, size_t... Indexes> template <typename... Args, size_t... Indexes>
void apply_func(const Func& func, const size_t arg, std::integer_sequence<size_t, Indexes...>, Args&&... args) void create_conv_funcs(std::integer_sequence<size_t, Indexes...>, Args&&... args)
{ {
((handle_func<Indexes>(func, arg, std::forward<Args>(args))), ...); ((handle_func<Indexes>(std::forward<Args>(args))), ...);
} }
template <size_t index, typename Func, typename T> template <size_t index, typename T>
void handle_func(const Func& func, const size_t arg, T&& t) void handle_func(const T& t)
{ {
if (index == arg) m_arg_print_funcs[index] = [&t, this](std::ostream& stream, const fmt_spec_t& type)
func(std::forward<T>(t)); {
switch (type.sign)
{
case fmt_sign_t::SPACE:
if constexpr (std::is_arithmetic_v<T>)
{
if (t >= 0)
stream << ' ';
}
break;
case fmt_sign_t::PLUS:
if constexpr (std::is_arithmetic_v<T>)
{
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<T>)
{
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<T> || std::is_convertible_v<T, char>)
{
stream << static_cast<char>(t);
} else
{
stream << t;
}
break;
case fmt_type_t::GENERAL:
if constexpr (std::is_arithmetic_v<T>)
{
if (static_cast<u64>(t) > 10e12)
exponential(stream);
else
fixed(stream);
stream << t;
} else
{
stream << t;
}
break;
default:
handle_type(stream, type.type);
stream << t;
}
};
} }
[[nodiscard]] std::pair<i64, std::optional<fmt_type_t>> 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(); static void exponential(std::ostream& stream);
void fixed(); static void fixed(std::ostream& stream);
void compile(std::string fmt); void compile(std::string fmt);
std::optional<std::pair<size_t, size_t>> consume_until_fmt(); std::optional<std::pair<size_t, size_t>> consume_to_next_fmt();
void finish();
std::string m_fmt; std::string m_fmt;
std::stringstream m_stream; std::stringstream m_stream;
fmt_parser_t m_parser; fmt_parser_t m_parser;
// normal sections of string
std::vector<std::string_view> m_string_sections;
// processed format specs
std::vector<fmt_spec_t> m_fmt_specs;
std::vector<std::function<void(std::ostream&, const fmt_spec_t&)>> m_arg_print_funcs;
size_t m_last_fmt_pos = 0; size_t m_last_fmt_pos = 0;
size_t m_arg_pos = 0; size_t m_arg_pos = 0;
}; };

@ -1 +1 @@
Subproject commit 7ef2e733416953b222851f9a360d7fc72d068ee5 Subproject commit 93201da2ba5a6aba0a6e57ada64973555629b3e3

View File

@ -16,6 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include <iostream> #include <iostream>
#include <sstream>
#include <blt/logging/fmt_tokenizer.h> #include <blt/logging/fmt_tokenizer.h>
namespace blt::logging namespace blt::logging
@ -97,10 +98,7 @@ namespace blt::logging
void fmt_parser_t::parse_fmt_field() void fmt_parser_t::parse_fmt_field()
{ {
if (!has_next()) if (!has_next())
{ throw std::runtime_error("Expected token when parsing format field");
std::cerr << "Expected token when parsing format field" << std::endl;
std::exit(EXIT_FAILURE);
}
const auto [type, value] = peek(); const auto [type, value] = peek();
if (type == fmt_token_type::COLON) if (type == fmt_token_type::COLON)
{ {
@ -118,16 +116,14 @@ namespace blt::logging
parse_fmt_spec_stage_1(); parse_fmt_spec_stage_1();
} }
else else
{ throw std::runtime_error("Expected ':' when parsing format field after arg id!");
std::cerr << "Expected ':' when parsing format field after arg id!" << std::endl;
std::exit(EXIT_FAILURE);
}
} }
} }
else else
{ {
std::cerr << "Expected unknown token '" << static_cast<u8>(type) << "' value '" << value << "' when parsing format field" << std::endl; std::stringstream ss;
std::exit(EXIT_FAILURE); ss << "Expected unknown token '" << static_cast<u8>(type) << "' value '" << value << "' when parsing format field";
throw std::runtime_error(ss.str());
} }
if (has_next()) if (has_next())
parse_type(); parse_type();
@ -136,15 +132,13 @@ namespace blt::logging
void fmt_parser_t::parse_arg_id() void fmt_parser_t::parse_arg_id()
{ {
if (!has_next()) if (!has_next())
{ throw std::runtime_error("Missing token when parsing arg id");
std::cerr << "Missing token when parsing arg id" << std::endl;
std::exit(EXIT_FAILURE);
}
const auto [type, value] = next(); const auto [type, value] = next();
if (type != fmt_token_type::NUMBER) if (type != fmt_token_type::NUMBER)
{ {
std::cerr << "Expected number when parsing arg id, unexpected value '" << value << '\'' << std::endl; std::stringstream ss;
std::exit(EXIT_FAILURE); 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)); 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::STRING:
case fmt_token_type::COLON: case fmt_token_type::COLON:
std::cerr << "(Stage 1) Invalid token type " << static_cast<u8>(type) << " value " << value << std::endl; {
std::exit(EXIT_FAILURE); std::stringstream ss;
ss << "(Stage 1) Invalid token type " << static_cast<u8>(type) << " value " << value;
throw std::runtime_error(ss.str());
}
case fmt_token_type::NUMBER: case fmt_token_type::NUMBER:
parse_width(); parse_width();
parse_fmt_spec_stage_3(); parse_fmt_spec_stage_3();
@ -191,8 +188,11 @@ namespace blt::logging
case fmt_token_type::MINUS: case fmt_token_type::MINUS:
case fmt_token_type::PLUS: case fmt_token_type::PLUS:
case fmt_token_type::SPACE: case fmt_token_type::SPACE:
std::cerr << "(Stage 2) Invalid token type " << static_cast<u8>(type) << " value " << value << std::endl; {
std::exit(EXIT_FAILURE); std::stringstream ss;
ss << "(Stage 2) Invalid token type " << static_cast<u8>(type) << " value " << value;
throw std::runtime_error(ss.str());
}
case fmt_token_type::NUMBER: case fmt_token_type::NUMBER:
parse_width(); parse_width();
parse_fmt_spec_stage_3(); parse_fmt_spec_stage_3();
@ -217,8 +217,11 @@ namespace blt::logging
case fmt_token_type::PLUS: case fmt_token_type::PLUS:
case fmt_token_type::SPACE: case fmt_token_type::SPACE:
case fmt_token_type::NUMBER: case fmt_token_type::NUMBER:
std::cerr << "(Stage 3) Invalid token type " << static_cast<u8>(type) << " value " << value << std::endl; {
std::exit(EXIT_FAILURE); std::stringstream ss;
ss << "(Stage 3) Invalid token type " << static_cast<u8>(type) << " value " << value;
throw std::runtime_error(ss.str());
}
case fmt_token_type::DOT: case fmt_token_type::DOT:
consume(); consume();
parse_precision(); parse_precision();
@ -231,8 +234,9 @@ namespace blt::logging
auto [_, value] = next(); auto [_, value] = next();
if (value.size() > 1) if (value.size() > 1)
{ {
std::cerr << "Sign contains more than one character, we are not sure how to interpret this. Value '" << value << "'\n"; std::stringstream ss;
std::exit(EXIT_FAILURE); 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]) switch (value[0])
{ {
@ -246,8 +250,11 @@ namespace blt::logging
m_spec.sign = fmt_sign_t::SPACE; m_spec.sign = fmt_sign_t::SPACE;
break; break;
default: 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() void fmt_parser_t::parse_precision()
{ {
if (!has_next()) if (!has_next())
{ throw std::runtime_error("Missing token when parsing precision");
std::cerr << "Missing token when parsing precision" << std::endl;
std::exit(EXIT_FAILURE);
}
auto [_, value] = next(); auto [_, value] = next();
m_spec.precision = std::stoll(std::string(value)); m_spec.precision = std::stoll(std::string(value));
} }
@ -273,15 +277,13 @@ namespace blt::logging
void fmt_parser_t::parse_type() void fmt_parser_t::parse_type()
{ {
if (!has_next()) if (!has_next())
{ throw std::runtime_error("Missing token when parsing type");
std::cerr << "Missing token when parsing type" << std::endl;
std::exit(EXIT_FAILURE);
}
auto [_, value] = next(); auto [_, value] = next();
if (value.size() != 1) if (value.size() != 1)
{ {
std::cerr << "Type contains more than one character, we are not sure how to interpret this value '" << value << "'\n"; std::stringstream ss;
std::exit(EXIT_FAILURE); 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()); m_spec.uppercase = std::isupper(value.front());
switch (value.front()) switch (value.front())
@ -320,8 +322,9 @@ namespace blt::logging
m_spec.type = fmt_type_t::GENERAL; m_spec.type = fmt_type_t::GENERAL;
break; break;
default: default:
std::cerr << "Invalid type " << value << std::endl; std::stringstream ss;
std::exit(EXIT_FAILURE); ss << "Invalid type " << value;
throw std::runtime_error(ss.str());
} }
} }
} }

View File

@ -23,122 +23,147 @@
namespace blt::logging namespace blt::logging
{ {
struct logging_thread_context_t struct logging_thread_context_t
{ {
logger_t logger; logger_t logger;
}; };
std::string logger_t::to_string() std::string logger_t::to_string()
{ {
auto str = m_stream.str(); auto str = m_stream.str();
m_stream.str(""); m_stream.str("");
m_stream.clear(); m_stream.clear();
return str; return str;
} }
std::pair<i64, std::optional<fmt_type_t>> logger_t::handle_fmt(const std::string_view fmt) void logger_t::setup_stream(const fmt_spec_t& spec)
{ {
const auto spec = m_parser.parse(fmt); if (spec.leading_zeros)
if (spec.leading_zeros) m_stream << std::setfill('0');
m_stream << std::setfill('0'); else
else m_stream << std::setfill(' ');
m_stream << std::setfill(' '); if (spec.width > 0)
if (spec.width > 0) m_stream << std::setw(static_cast<i32>(spec.width));
m_stream << std::setw(static_cast<i32>(spec.width)); else
else m_stream << std::setw(0);
m_stream << std::setw(0); if (spec.precision > 0)
if (spec.precision > 0) m_stream << std::setprecision(static_cast<i32>(spec.precision));
m_stream << std::setprecision(static_cast<i32>(spec.precision)); else
else m_stream << std::setprecision(static_cast<int>(std::cout.precision()));
m_stream << std::setprecision(2); if (spec.uppercase)
if (spec.uppercase) m_stream << std::uppercase;
m_stream << std::uppercase; else
else m_stream << std::nouppercase;
m_stream << std::nouppercase; }
std::optional<fmt_type_t> 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::exponential() void logger_t::process_strings()
{ {
m_stream << std::scientific; 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<i64>(m_arg_pos++);
void logger_t::fixed() setup_stream(*spec_it);
{ m_arg_print_funcs[arg_pos](m_stream, *spec_it);
m_stream << std::fixed; }
} m_stream << *str_it;
}
void logger_t::compile(std::string fmt) void logger_t::handle_type(std::ostream& stream, const fmt_type_t type)
{ {
m_fmt = std::move(fmt); switch (type)
m_last_fmt_pos = 0; {
m_arg_pos = 0; case fmt_type_t::DECIMAL:
m_stream.str(""); stream << std::dec;
m_stream.clear(); 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<std::pair<size_t, size_t>> logger_t::consume_until_fmt() void logger_t::exponential(std::ostream& stream)
{ {
const auto begin = m_fmt.find('{', m_last_fmt_pos); stream << std::scientific;
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<i64>(begin) - 5, 0l)) << std::endl;
std::exit(EXIT_FAILURE);
}
m_stream << std::string_view(m_fmt.data() + static_cast<ptrdiff_t>(m_last_fmt_pos), begin - m_last_fmt_pos);\
m_last_fmt_pos = end + 1;
return std::pair{begin, end};
}
void logger_t::finish() void logger_t::fixed(std::ostream& stream)
{ {
m_stream << std::string_view(m_fmt.data() + static_cast<ptrdiff_t>(m_last_fmt_pos), m_fmt.size() - m_last_fmt_pos); stream << std::fixed;
m_last_fmt_pos = m_fmt.size(); }
}
logger_t& get_global_logger() void logger_t::compile(std::string fmt)
{ {
thread_local logging_thread_context_t context; m_fmt = std::move(fmt);
return context.logger; 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<ptrdiff_t>(begin) + 1, end - begin - 1}));
else
m_fmt_specs.emplace_back();
last_pos = static_cast<ptrdiff_t>(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::optional<std::pair<size_t, size_t>> logger_t::consume_to_next_fmt()
{ {
std::cout << 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<i64>(begin) - 5, 0l));
throw std::runtime_error(ss.str());
}
m_last_fmt_pos = end + 1;
return std::pair{begin, end};
}
void newline() logger_t& get_global_logger()
{ {
std::cout << std::endl; 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;
}
} }

View File

@ -25,12 +25,16 @@ int main()
// blt::logging::println("{} | {} | {} | {}", blt::type_string<endl_t>()); // blt::logging::println("{} | {} | {} | {}", blt::type_string<endl_t>());
blt::logging::println("This is a println!"); 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 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 '{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 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 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 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"); // blt::logging::println("This is println {}\twith a std::endl in the middle of it");
} }