diff --git a/CMakeLists.txt b/CMakeLists.txt index f8ac431..cb1c485 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 3.0.6) +set(BLT_VERSION 5.2.34) set(BLT_TARGET BLT) @@ -19,6 +19,7 @@ option(BUILD_PROFILING "Build the BLT profiler extension" ON) option(BUILD_FS "Build the BLT FS utilities including the NBT + eNBT extension" ON) option(BUILD_PARSE "Build the BLT parsers" ON) option(BUILD_FORMAT "Build the BLT formatters" ON) +option(BUILD_LOGGING "Build the BLT logging utilities" ON) option(BUILD_TESTS "Build the BLT test set" OFF) @@ -88,8 +89,14 @@ if (${BUILD_FORMAT}) message(STATUS "Building ${Yellow}format${ColourReset} cxx files") file(GLOB_RECURSE FORMAT_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/blt/format/*.cpp") else () - set(FORMAT_FILES "" - include/blt/std/iterator.h) + set(FORMAT_FILES "") +endif () + +if (${BUILD_LOGGING}) + message(STATUS "Building ${Yellow}logging${ColourReset} cxx files") + file(GLOB_RECURSE LOGGING_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/blt/logging/*.cpp") +else () + set(LOGGING_FILES "") endif () if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/libraries/parallel-hashmap) @@ -111,7 +118,7 @@ endif () include_directories(include/) include_directories(${CMAKE_CURRENT_BINARY_DIR}/config/) -add_library(${BLT_TARGET} ${STD_FILES} ${PROFILING_FILES} ${FS_FILES} ${PARSE_FILES} ${FORMAT_FILES}) +add_library(${BLT_TARGET} ${STD_FILES} ${PROFILING_FILES} ${FS_FILES} ${PARSE_FILES} ${FORMAT_FILES} ${LOGGING_FILES}) string(REPLACE "+" "\\+" escaped_source ${CMAKE_CURRENT_SOURCE_DIR}) string(APPEND escaped_source "/src/blt/.*/") @@ -166,8 +173,9 @@ install(TARGETS ${BLT_TARGET} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) -macro(blt_add_project name source type) +macro(blt_add_test name source type) + message("Adding project ${name} of type ${type}" DEBUG) project(${name}-${type}) add_executable(${name}-${type} ${source}) @@ -178,8 +186,10 @@ macro(blt_add_project name source type) target_link_libraries(${name}-${type} PRIVATE BLT) - target_compile_options(${name}-${type} PRIVATE -Wall -Wextra -Wpedantic -Wno-comment) - target_link_options(${name}-${type} PRIVATE -Wall -Wextra -Wpedantic -Wno-comment) + if (NOT MSVC) + target_compile_options(${name}-${type} PRIVATE -Wall -Wextra -Wpedantic -Wno-comment) + target_link_options(${name}-${type} PRIVATE -Wall -Wextra -Wpedantic -Wno-comment) + endif() target_compile_definitions(${name}-${type} PRIVATE BLT_DEBUG_LEVEL=${DEBUG_LEVEL}) if (${TRACK_ALLOCATIONS}) @@ -212,7 +222,9 @@ endmacro() if (${BUILD_TESTS}) message("Building tests for version ${BLT_VERSION}") - blt_add_project(blt-iterator tests/iterator_tests.cpp test) + blt_add_test(blt_iterator tests/iterator_tests.cpp test) + blt_add_test(blt_argparse tests/argparse_tests.cpp test) + blt_add_test(blt_logging tests/logger_tests.cpp test) message("Built tests") endif () diff --git a/README.md b/README.md index 4c16a4d..026d561 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,50 @@ -# **BLT v0.20** +# **BLT v5.1** A C++17 common utilities library to make thing easy! ![Icon](icon_large.png) --- -# ***Features*** -- ## blt/fs - - ### loader.h - - std::string blt::fs::getFile(std::string_view path) - - Gets the entire file as a string. - - std::vector\ blt::fs::getLinesFromFile(std::string_view path) - - Gets the entire file as a string, then splits on the new line character. Then returns a vector of those lines - - std::vector\ blt::fs::recursiveInclude(std::string_view path, std::string include_header, std::vector guards); - - Recursively include in order based on the include string (include_header) marked by the include guards - - Defaults to C/C++/GLSL preprocessor style (Was designed for GLSL) - - std::string blt::fs::loadBrainFuckFile(const std::string& path) - - Load a brainfuck file recursively, uses ~ to mark the include path - - ### nbt.h - - probably needs to be remade (TODO) +# Features + +## BLT Format + +This module provides general string formatting utilities. *Note: this folder contains mostly outdated library files and will be updated in the future.* + +### Files + +- **boxing.h** + Simple utility for drawing boxes around blocks of text. + +- **format.h** + Legacy library file containing various utilities for creating formatted output. Also includes methods for writing Java UTF8 strings. + +## BLT Filesystem +This module provides helper classes for filesystem objects. It seeks to offer an interface that is simpler than the one provided by the standard library. +Specifically, the number of functions required to implement is significantly lower, +and the interface is generally cleaner. Eventually, this module aims to support various file formats, +such as Minecraft's NBT system. Currently, there is an existing NBT file, but it was written when I was first learning C++. + +### Files + +- **filesystem.h** + This is the base file which includes all other files. You should use the other options as this can be a heavy file to include + +- **path_helper.h** + This file provides functions for interfacing with paths. Specifically, as of this moment it only provides an interface for getting the base name of a file path. + +- **loader.h** + - `std::string blt::fs::getFile(std::string_view path)` + - Gets the entire file as a string. + - `std::vector\ blt::fs::getLinesFromFile(std::string_view path)` + - Gets the entire file as a string, then splits on the new line character. Then returns a vector of those lines + - `std::vector\ blt::fs::recursiveInclude(std::string_view path, std::string include_header, std::vector guards)` + - Recursively include in order based on the include string (include_header) marked by the include guards + - Defaults to C/C++/GLSL preprocessor style (Was designed for GLSL) + - `std::string blt::fs::loadBrainFuckFile(const std::string& path)` + - Load a brainfuck file recursively, uses ~ to mark the include path +- ### nbt.h + - probably needs to be remade (TODO) - ## blt/math - ### averages.h - blt::averagizer_o_matic diff --git a/cloc.sh b/cloc.sh old mode 100644 new mode 100755 diff --git a/cmake/warnings.cmake b/cmake/warnings.cmake index 035c0df..315d5fb 100644 --- a/cmake/warnings.cmake +++ b/cmake/warnings.cmake @@ -23,7 +23,9 @@ elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") message(STATUS "GCC libs: ${Green}stdc++fs${ColourReset}") target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -Wpedantic -fdiagnostics-color=always) target_link_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -Wpedantic -fdiagnostics-color=always) - target_link_options(${PROJECT_NAME} PUBLIC -rdynamic) + if (NOT WIN32) + target_link_options(${PROJECT_NAME} PUBLIC -rdynamic) + endif() target_link_libraries(${PROJECT_NAME} PUBLIC stdc++fs) include(GNUInstallDirs) elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Intel") diff --git a/commit.py.save b/commit.py.save deleted file mode 100644 index 3ecb2e0..0000000 --- a/commit.py.save +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/python3 - -import subprocess - -#--------------------------------------- -# CONFIG -#--------------------------------------- - -VERSION_BEGIN_STR = "set(BLT_VERSION " -VERSION_END_STR = ")" - -#--------------------------------------- -# DO NOT TOUCH -#--------------------------------------- - -type = input("What kind of commit is this ((M)ajor, (m)inor, (p)atch)? ") - -def load_cmake(): - cmake_file = open("CMakeLists.txt", 'r') - cmake_text = cmake_file.read() - cmake_file.close() - return cmake_text - -def write_cmake(cmake_text): - cmake_file = open("CMakeLists.txt", 'w') - cmake_file.write(cmake_text) - cmake_file.close() - -def get_version(cmake_text): - begin = cmake_text.find(VERSION_BEGIN_STR) + len(find_text) - end = cmake_text.find(VERSION_END_STR, begin) - return (cmake_text[begin:end], begin, end) - -def split_version(cmake_text): - version, begin, end = get_version(cmake_text) - version_parts = version.split('.') - return (version_parts, begin, end) - -def recombine(cmake_text, version_parts, begin, end): - constructed_version = version_parts[0] + '.' + version_parts[1] + '.' + version_parts[2] - constructed_text_begin = cmake_text[0:begin] - constrcuted_text_end = cmake_text[end::] - return constructed_text_begin + constructed_version + constrcuted_text_end - - -def inc_major(cmake_text): - version_parts, begin, end = split_version(cmake_text) - version_parts[0] = str(int(version_parts[0]) + 1) - return recombine(cmake_text, version_parts, begin, end) - -def inc_minor(cmake_text): - version_parts, begin, end = split_version(cmake_text) - version_parts[1] = str(int(version_parts[1]) + 1) - return recombine(cmake_text, version_parts, begin, end) - -def inc_patch(cmake_text): - version_parts, begin, end = split_version(cmake_text) - version_parts[2] = str(int(version_parts[2]) + 1) - return recombine(cmake_text, version_parts, begin, end) - -if type.startswith('M'): - print("Selected major") - write_cmake(inc_major(load_cmake())) -elif type.startswith('m'): - print("Selected minor") - write_cmake(inc_minor(load_cmake())) -elif type.startswith('p') or type.startswith('P') or len(type) == 0: - print("Selected patch") - write_cmake(inc_patch(load_cmake())) - -#subprocess.call("./py_commit_helper.sh") -subprocess.call("git", "add", "*") -subprocess.call("git", "commit") -subprocess.call("sh -e 'git remote | xargs -L1 git push --all'") diff --git a/include/blt/compatibility.h b/include/blt/compatibility.h index b10d4f6..a3dfc45 100644 --- a/include/blt/compatibility.h +++ b/include/blt/compatibility.h @@ -51,4 +51,12 @@ #error Filesystem ops not supported!\ #endif +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) + #define BLT_OS_WINDOWS +#elif defined(__linux__) || defined(__unix__) + #define BLT_OS_LINUX +#else + #define BLT_OS_UNKNOWN +#endif + #endif //BLT_COMPATIBILITY_H diff --git a/include/blt/format/boxing.h b/include/blt/format/boxing.h index aaaa1bd..0df292e 100644 --- a/include/blt/format/boxing.h +++ b/include/blt/format/boxing.h @@ -20,7 +20,7 @@ #define BLT_BOXING_H #include -#include +#include #include namespace blt @@ -84,25 +84,6 @@ namespace blt Logger& logger; }; - template<> - class log_box_t : detail::log_box_base_t - { - public: - log_box_t(blt::logging::logger logger, std::string_view title, blt::size_t padding = 0): detail::log_box_base_t(title, padding), logger(logger) - { - make_full_title(logger); - logger << '\n'; - } - - ~log_box_t() - { - make_full_width_line(logger); - logger << '\n'; - } - private: - blt::logging::logger logger; - }; - template log_box_t(Logger&& logger, std::string_view, blt::size_t) -> log_box_t; diff --git a/include/blt/fs/file_writers.h b/include/blt/fs/file_writers.h new file mode 100644 index 0000000..52ef476 --- /dev/null +++ b/include/blt/fs/file_writers.h @@ -0,0 +1,146 @@ +#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_FS_BOUNDED_WRITER_H +#define BLT_FS_BOUNDED_WRITER_H + +#include +#include +#include +#include +#include +#include + +namespace blt::fs +{ + inline auto basic_naming_function = [](const size_t invocation, std::string prefix) { + prefix += '-'; + prefix += std::to_string(invocation); + prefix += ".txt"; + return prefix; + }; + + using naming_function_t = std::function; + + class fwriter_t : public writer_t + { + public: + explicit fwriter_t(const std::string& name, std::string mode = "ab"): m_mode(std::move(mode)) + { + fwriter_t::newfile(name); + } + + // create a writer without creating a new file. Writing without calling newfile is UB + explicit fwriter_t(std::string mode = "ab"): m_mode(std::move(mode)) + {} + + i64 write(const char* buffer, size_t bytes) override; + + virtual void newfile(const std::string& new_name); + + void flush() override; + + protected: + std::string m_mode; + FILE* m_file = nullptr; + }; + + // ReSharper disable once CppClassCanBeFinal + class buffered_writer : public fwriter_t + { + public: + explicit buffered_writer(const std::string& name, size_t buffer_size = 1024 * 128); + explicit buffered_writer(size_t buffer_size = 1024 * 128); + + i64 write(const char* buffer, size_t bytes) override; + + void flush() override; + + void newfile(const std::string& new_name) override; + + protected: + size_t m_current_pos = 0; + std::vector m_buffer; + }; + + /** + * Creates a bounded writer where after a specified number of bytes a new file will be opened and written to instead. + */ + // ReSharper disable once CppClassCanBeFinal + class bounded_writer : public fwriter_t + { + public: + explicit bounded_writer(fwriter_t& writer, std::optional base_name, size_t max_size = 1024 * 1024 * 10, + naming_function_t naming_function = basic_naming_function); + + i64 write(const char* buffer, size_t bytes) override; + + void newfile(const std::string& new_name) override; + + void flush() override; + + private: + fwriter_t* m_writer; + std::optional m_base_name; + size_t m_current_invocation = 0; + size_t m_max_size; + size_t m_currently_written = 0; + // inputs: current invocation, then basename string + // returns: name of the file to write to + naming_function_t m_naming_function; + }; + + struct time_t + { + i32 year = 0, month = 0, day = 1, hour = -1; + + time_t(const i32 year, const i32 month, const i32 day, const i32 hour) : year{year}, month{month}, day{day}, hour{hour} + {} + + time_t(const i32 year, const i32 month, const i32 day) : year{year}, month{month}, day{day} + {} + + time_t() = default; + }; + + // ReSharper disable once CppClassCanBeFinal + class rotating_writer : public fwriter_t + { + public: + rotating_writer(fwriter_t& writer, time_t period); + + i64 write(const char* buffer, size_t bytes) override; + + void flush() override; + + void newfile(const std::string& new_name) override; + + void newfile(); + + void check_for_time(); + + static time_t get_current_time(); + + private: + fwriter_t* m_writer; + time_t m_period; + time_t m_last_time; + }; +} + +#endif //BLT_FS_BOUNDED_WRITER_H diff --git a/include/blt/fs/filesystem.h b/include/blt/fs/filesystem.h index 17bdbd2..096902c 100644 --- a/include/blt/fs/filesystem.h +++ b/include/blt/fs/filesystem.h @@ -19,145 +19,15 @@ #ifndef BLT_FILESYSTEM_H #define BLT_FILESYSTEM_H -#include -#include +#include +#include +#include +#include +#include namespace blt::fs { - /** - * A simple interface which provides a way of reading the next block of data from a resource. - * The interface provides a single function "read" which will read a specified number of bytes into the buffer. - * The implementation for this could be fstreams, zlib deflate, or any method filesystem access. - * Reading of a large number of bytes ( > block size) is guaranteed to not significantly increase the read time and will likely result in a - * direct passthrough to the underlying system. Small reads will be buffered, hence the name "block" reader. - */ - class block_reader - { - protected: - // 32768 block size seems the fastest on my system - unsigned long m_bufferSize; - public: - explicit block_reader(size_t bufferSize): m_bufferSize(bufferSize) - {} - - /** - * Reads bytes from the internal filesystem implementation - * @param buffer buffer to copy the read bytes into - * @param bytes number of bytes to read - * @return status code. non-zero return codes indicates a failure has occurred. - */ - virtual int read(char* buffer, size_t bytes) = 0; - - virtual size_t gcount() = 0; - - virtual char get() - { - char c[1]; - read(c, 1); - return c[0]; - } - }; - - /** - * A buffered block writer without a definite backend implementation. Exactly the same as a block_reader but for writing to the filesystem. - */ - class block_writer - { - protected: - unsigned long m_bufferSize; - public: - explicit block_writer(unsigned long bufferSize): m_bufferSize(bufferSize) - {} - - /** - * Writes the bytes to the filesystem backend implementation - * @param buffer bytes to write - * @param bytes number of bytes to write - * @return non-zero code if failure - */ - virtual int write(char* buffer, size_t bytes) = 0; - - virtual int put(char c) - { - char a[1]; - a[0] = c; - return write(a, 1); - } - - /** - * Ensures that the internal buffer is written to the filesystem. - */ - virtual void flush() = 0; - }; - - /** - * fstream implementation of the block reader. - */ - class fstream_block_reader : public block_reader - { - private: - std::fstream& m_stream; - char* m_buffer = nullptr; - size_t readIndex = 0; - public: - explicit fstream_block_reader(std::fstream& stream, size_t bufferSize = 131072); - - explicit fstream_block_reader(fstream_block_reader& copy) = delete; - - explicit fstream_block_reader(fstream_block_reader&& move) = delete; - - fstream_block_reader& operator=(const fstream_block_reader& copy) = delete; - - fstream_block_reader& operator=(const fstream_block_reader&& move) = delete; - - int read(char* buffer, size_t bytes) override; - - size_t gcount() final - { - return m_stream.gcount(); - } - - ~fstream_block_reader() - { - delete[] m_buffer; - } - }; - - class fstream_block_writer : public block_writer - { - private: - std::fstream& m_stream; - char* m_buffer; - size_t writeIndex = 0; - - void flush_internal(); - - public: - explicit fstream_block_writer(std::fstream& stream, size_t bufferSize = 131072): - block_writer(bufferSize), m_stream(stream), m_buffer(new char[bufferSize]) - {} - - explicit fstream_block_writer(fstream_block_writer& copy) = delete; - - explicit fstream_block_writer(fstream_block_writer&& move) = delete; - - fstream_block_writer& operator=(const fstream_block_writer& copy) = delete; - - fstream_block_writer& operator=(const fstream_block_writer&& move) = delete; - - int write(char* buffer, size_t bytes) override; - - inline void flush() override - { - flush_internal(); - } - - ~fstream_block_writer() - { - flush_internal(); - delete[] m_buffer; - } - }; + } #endif //BLT_FILESYSTEM_H diff --git a/include/blt/fs/fwddecl.h b/include/blt/fs/fwddecl.h new file mode 100644 index 0000000..06359be --- /dev/null +++ b/include/blt/fs/fwddecl.h @@ -0,0 +1,77 @@ +#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_FS_FWDDECL_H +#define BLT_FS_FWDDECL_H + +#include + +namespace blt::fs +{ + /** + * A simple interface which provides a way of reading the next block of data from a resource. This is designed to replace the overly complex + * std::ostream + */ + class reader_t + { + public: + virtual ~reader_t() = default; + explicit reader_t() = default; + + reader_t(const reader_t&) = delete; + reader_t& operator=(const reader_t&) = delete; + + /** + * Reads bytes from the internal filesystem implementation + * @param buffer buffer to copy the read bytes into + * @param bytes number of bytes to read + * @return number of bytes read, or negative value if error. Errors are not required and can just return 0 + */ + virtual i64 read(char* buffer, size_t bytes) = 0; + }; + + /** + * A block writer without a definite backend implementation. Exactly the same as a block_reader but for writing to the filesystem. + * this is designed to replace the overly complex std::istream + */ + class writer_t + { + public: + virtual ~writer_t() = default; + explicit writer_t() = default; + + writer_t(const writer_t&) = delete; + writer_t& operator=(const writer_t&) = delete; + + /** + * Writes the bytes to the filesystem backend implementation + * @param buffer bytes to write + * @param bytes number of bytes to write + * @return number of bytes, or negative value if error. Zero is also a valid return, not indicating error in itself but can be the result of one. + */ + virtual i64 write(const char* buffer, size_t bytes) = 0; + + /** + * Optional flush command which syncs the underlying objects + */ + virtual void flush() + {}; + }; +} + +#endif //BLT_FS_FWDDECL_H diff --git a/include/blt/fs/loader.h b/include/blt/fs/loader.h index 991c249..d5eb966 100644 --- a/include/blt/fs/loader.h +++ b/include/blt/fs/loader.h @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include namespace blt::fs diff --git a/include/blt/fs/nbt.h b/include/blt/fs/nbt.h index 0a04535..d94d988 100644 --- a/include/blt/fs/nbt.h +++ b/include/blt/fs/nbt.h @@ -15,26 +15,26 @@ #include "blt/format/format.h" #include "blt/fs/filesystem.h" -#include "blt/std/logging.h" +#include "blt/logging/logging.h" #include "blt/std/memory.h" #include namespace blt::nbt { - void writeUTF8String(blt::fs::block_writer& stream, const std::string& str); + void writeUTF8String(blt::fs::writer_t& stream, const std::string& str); - std::string readUTF8String(blt::fs::block_reader& stream); + std::string readUTF8String(blt::fs::reader_t& stream); template - inline static void writeData(blt::fs::block_writer& out, const T& d){ + inline static void writeData(blt::fs::writer_t& out, const T& d){ char data[sizeof(T)]; mem::toBytes(d, data); out.write(data, sizeof(T)); } template - inline static void readData(blt::fs::block_reader& in, T& d) { + inline static void readData(blt::fs::reader_t& in, T& d) { char data[sizeof(T)]; in.read(data, sizeof(T)); mem::fromBytes(data, &d); @@ -63,12 +63,12 @@ namespace blt::nbt { public: explicit tag_t(nbt_tag type): type(type) {}; explicit tag_t(nbt_tag type, std::string name): type(type), name(std::move(name)) {} - virtual void writePayload(blt::fs::block_writer& out) = 0; - virtual void readPayload(blt::fs::block_reader& in) = 0; - void writeName(blt::fs::block_writer& out) { + virtual void writePayload(blt::fs::writer_t& out) = 0; + virtual void readPayload(blt::fs::reader_t& in) = 0; + void writeName(blt::fs::writer_t& out) { writeUTF8String(out, name); } - void readName(blt::fs::block_reader& in) { + void readName(blt::fs::reader_t& in) { name = readUTF8String(in); } [[nodiscard]] inline nbt_tag getType() const { @@ -87,11 +87,11 @@ namespace blt::nbt { public: explicit tag(nbt_tag type): tag_t(type) {} tag(nbt_tag type, std::string name, T t): tag_t(type, std::move(name)), t(std::move(t)) {} - void writePayload(blt::fs::block_writer& out) override { + void writePayload(blt::fs::writer_t& out) override { if constexpr(std::is_arithmetic::value) writeData(out, t); } - void readPayload(blt::fs::block_reader& in) override { + void readPayload(blt::fs::reader_t& in) override { if constexpr(std::is_arithmetic::value) readData(in, t); } @@ -102,9 +102,9 @@ namespace blt::nbt { class tag_end : public tag { public: - void writePayload(blt::fs::block_writer&) final {} + void writePayload(blt::fs::writer_t&) final {} // nothing to read - void readPayload(blt::fs::block_reader&) final {} + void readPayload(blt::fs::reader_t&) final {} }; class tag_byte : public tag { @@ -147,13 +147,13 @@ namespace blt::nbt { public: tag_byte_array(): tag(nbt_tag::BYTE_ARRAY) {} tag_byte_array(const std::string& name, const std::vector& v): tag(nbt_tag::BYTE_ARRAY, name, v) {} - void writePayload(blt::fs::block_writer& out) final { + void writePayload(blt::fs::writer_t& out) final { auto length = (int32_t) t.size(); writeData(out, length); // TODO on the writer (remove need for cast + more std::fstream functions) out.write(reinterpret_cast(t.data()), length); } - void readPayload(blt::fs::block_reader& in) final { + void readPayload(blt::fs::reader_t& in) final { int32_t length; readData(in, length); t.reserve(length); @@ -165,10 +165,10 @@ namespace blt::nbt { public: tag_string(): tag(nbt_tag::STRING) {} tag_string(const std::string& name, const std::string& s): tag(nbt_tag::STRING, name, s) {} - void writePayload(blt::fs::block_writer& out) final { + void writePayload(blt::fs::writer_t& out) final { writeUTF8String(out, t); } - void readPayload(blt::fs::block_reader& in) final { + void readPayload(blt::fs::reader_t& in) final { t = readUTF8String(in); } }; @@ -177,13 +177,13 @@ namespace blt::nbt { public: tag_int_array(): tag(nbt_tag::INT_ARRAY) {} tag_int_array(const std::string& name, const std::vector& v): tag(nbt_tag::INT_ARRAY, name, v) {} - void writePayload(blt::fs::block_writer& out) final { + void writePayload(blt::fs::writer_t& out) final { auto length = (int32_t) t.size(); writeData(out, length); for (int i = 0; i < length; i++) writeData(out, t[i]); } - void readPayload(blt::fs::block_reader& in) final { + void readPayload(blt::fs::reader_t& in) final { int32_t length; readData(in, length); t.reserve(length); @@ -196,13 +196,13 @@ namespace blt::nbt { public: tag_long_array(): tag(nbt_tag::LONG_ARRAY) {} tag_long_array(const std::string& name, const std::vector& v): tag(nbt_tag::LONG_ARRAY, name, v) {} - void writePayload(blt::fs::block_writer& out) final { + void writePayload(blt::fs::writer_t& out) final { auto length = (int32_t) t.size(); writeData(out, length); for (int i = 0; i < length; i++) writeData(out, t[i]); } - void readPayload(blt::fs::block_reader& in) final { + void readPayload(blt::fs::reader_t& in) final { int32_t length; readData(in, length); t.reserve(length); @@ -262,7 +262,7 @@ namespace blt::nbt { public: tag_list(): tag(nbt_tag::LIST) {} tag_list(const std::string& name, const std::vector& v): tag(nbt_tag::LIST, name, v) {} - void writePayload(blt::fs::block_writer& out) final { + void writePayload(blt::fs::writer_t& out) final { if (t.empty()) writeData(out, (char)nbt_tag::END); else @@ -273,7 +273,7 @@ namespace blt::nbt { v->writePayload(out); } - void readPayload(blt::fs::block_reader& in) final { + void readPayload(blt::fs::reader_t& in) final { char id; int32_t length; readData(in, id); @@ -305,7 +305,7 @@ namespace blt::nbt { auto& tag = t[i]; T t; if (tag->getType() != t.getType()) { - BLT_WARN("Expected tag of type %d but got tag of type %d", (char)t.getType(), (char)tag->getType()); + BLT_WARN("Expected tag of type {:d} but got tag of type {:d}", (char)t.getType(), (char)tag->getType()); throw std::runtime_error("Requested Tag does not match stored type!"); } return dynamic_cast(tag); @@ -342,7 +342,7 @@ namespace blt::nbt { auto& tag = t[name]; T t; if (tag->getType() != t.getType()) { - BLT_WARN("Expected tag of type %d but got tag of type %d", (char)t.getType(), (char)tag->getType()); + BLT_WARN("Expected tag of type {:d} but got tag of type {:d}", (char)t.getType(), (char)tag->getType()); throw std::runtime_error("Requested Tag does not match stored type!"); } return dynamic_cast(tag); @@ -356,18 +356,20 @@ namespace blt::nbt { t[tag->getName()] = tag; } - void writePayload(blt::fs::block_writer& out) final { + void writePayload(blt::fs::writer_t& out) final { for (const auto& v : t){ auto tag = v.second; - out.put((char) tag->getType()); + auto c = (char) tag->getType(); + out.write(&c, 1); tag->writeName(out); tag->writePayload(out); } - out.put('\0'); + const char c = '\0'; + out.write(&c, 1); } - void readPayload(blt::fs::block_reader& in) final { + void readPayload(blt::fs::reader_t& in) final { char type; - while ((type = in.get()) != (char)nbt_tag::END){ + while ((in.read(&type, 1), type) != (char)nbt_tag::END){ auto* v = _internal_::toType(type); v->readName(in); v->readPayload(in); @@ -390,10 +392,10 @@ namespace blt::nbt { class NBTReader { private: - blt::fs::block_reader& reader; + blt::fs::reader_t& reader; tag_compound* root = nullptr; public: - explicit NBTReader(blt::fs::block_reader& reader): reader(reader) {} + explicit NBTReader(blt::fs::reader_t& reader): reader(reader) {} void read(); @@ -407,7 +409,7 @@ namespace blt::nbt { auto& tag = root->get()[name]; T t; if (tag->getType() != t.getType()) { - BLT_WARN("Expected tag of type %d but got tag of type %d", (char)t.getType(), (char)tag->getType()); + BLT_WARN("Expected tag of type {:d} but got tag of type {:d}", (char)t.getType(), (char)tag->getType()); throw std::runtime_error("Requested Tag does not match stored type!"); } return dynamic_cast(tag); @@ -419,9 +421,9 @@ namespace blt::nbt { class NBTWriter { private: - blt::fs::block_writer& writer; + blt::fs::writer_t& writer; public: - explicit NBTWriter(blt::fs::block_writer& writer): writer(writer) {} + explicit NBTWriter(blt::fs::writer_t& writer): writer(writer) {} /** * Write a compound tag and then DELETES the tag. If you don't wish for the memory to be freed, please use the reference version! * @param root root NBT tag to write, this function assumes ownership of this pointer. @@ -431,7 +433,8 @@ namespace blt::nbt { delete root; } void write(tag_compound& root){ - writer.put((char)nbt_tag::COMPOUND); + auto c = (char)nbt_tag::COMPOUND; + writer.write(&c, 1); root.writeName(writer); root.writePayload(writer); } diff --git a/include/blt/fs/path_helper.h b/include/blt/fs/path_helper.h new file mode 100644 index 0000000..40a01fe --- /dev/null +++ b/include/blt/fs/path_helper.h @@ -0,0 +1,39 @@ +#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_FS_PATH_HELPER_H +#define BLT_FS_PATH_HELPER_H + +#include +#include + +namespace blt::fs +{ + + std::string base_name(const std::string& str); + std::string_view base_name_sv(std::string_view str); + + std::string filename(const std::string& str); + std::string_view filename_sv(std::string_view str); + + std::string extension(const std::string& str); + std::string_view extension_sv(std::string_view str); + +} + +#endif //BLT_FS_PATH_HELPER_H diff --git a/include/blt/fs/stream_wrappers.h b/include/blt/fs/stream_wrappers.h new file mode 100644 index 0000000..dff7024 --- /dev/null +++ b/include/blt/fs/stream_wrappers.h @@ -0,0 +1,143 @@ +#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_FS_STREAM_WRAPPERS_H +#define BLT_FS_STREAM_WRAPPERS_H + +#include +#include +#include + +namespace blt::fs +{ + /** + * reader_t wrapper for fstream + */ + class fstream_reader_t final : public reader_t + { + public: + explicit fstream_reader_t(std::istream& stream); + + explicit fstream_reader_t(fstream_reader_t& copy) = delete; + + fstream_reader_t& operator=(const fstream_reader_t& copy) = delete; + + i64 read(char* buffer, size_t bytes) override; + + private: + std::istream* m_stream; + }; + + class fstream_writer_t final : public writer_t + { + public: + explicit fstream_writer_t(std::ostream& stream); + + explicit fstream_writer_t(fstream_writer_t& copy) = delete; + + fstream_writer_t& operator=(const fstream_writer_t& copy) = delete; + + i64 write(const char* buffer, size_t bytes) override; + + void flush() override; + + virtual ~fstream_writer_t() override // NOLINT + { + flush(); + } + + private: + std::ostream* m_stream; + }; + + class reader_wrapper_t + { + public: + explicit reader_wrapper_t(reader_t& reader): m_reader(&reader) + {} + + template + void read(T& out) + { + if (!m_reader->read(reinterpret_cast(&out), sizeof(T))) + throw std::runtime_error("Failed to read from reader"); + } + + template + friend reader_wrapper_t& operator>>(reader_wrapper_t& reader, T& t) + { + reader.read(t); + return reader; + } + + private: + reader_t* m_reader; + }; + + class writer_wrapper_t + { + public: + explicit writer_wrapper_t(writer_t& writer): m_writer(&writer) + {} + + template + void write(const T& t) + { + static_assert(std::is_trivially_copyable_v); + m_writer->write(reinterpret_cast(&t), sizeof(T)); + } + + template + friend writer_wrapper_t& operator<<(writer_wrapper_t& writer, const T& t) + { + writer.write(t); + return writer; + } + + private: + writer_t* m_writer; + }; + + class writer_string_wrapper_t + { + public: + explicit writer_string_wrapper_t(writer_t& writer): m_writer(&writer) + {} + + template + void write(const T& t) + { + std::stringstream ss; + ss << t; + const auto str = ss.str(); + m_writer->write(str.data(), str.size()); + } + + template + friend writer_string_wrapper_t& operator<<(writer_string_wrapper_t& writer, const T& t) + { + writer.write(t); + return writer; + } + + private: + writer_t* m_writer; + }; +} + +#endif //BLT_FS_STREAM_WRAPPERS_H diff --git a/include/blt/fs/threaded_writers.h b/include/blt/fs/threaded_writers.h new file mode 100644 index 0000000..293c397 --- /dev/null +++ b/include/blt/fs/threaded_writers.h @@ -0,0 +1,42 @@ +#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_FS_THREADED_WRITERS_H +#define BLT_FS_THREADED_WRITERS_H + +#include +#include + +namespace blt::fs +{ + // ReSharper disable once CppClassCanBeFinal + class concurrent_file_writer : public writer_t + { + public: + explicit concurrent_file_writer(writer_t* writer); + + i64 write(const char* buffer, size_t bytes) override; + + void flush() override; + private: + writer_t* m_writer; + std::mutex m_mutex; + }; +} + +#endif //BLT_FS_THREADED_WRITERS_H diff --git a/include/blt/iterator/common.h b/include/blt/iterator/common.h index d98ddeb..2781194 100644 --- a/include/blt/iterator/common.h +++ b/include/blt/iterator/common.h @@ -27,9 +27,42 @@ #include #include #include +#include namespace blt::iterator { + namespace detail + { + template + static auto forward_as_tuple(T&& t) -> decltype(auto) + { + using Decay = std::decay_t; + static_assert(!(meta::is_tuple_v || meta::is_pair_v), "Tuple or pair passed to forward_as_tuple! Must not be a tuple!"); + if constexpr (std::is_lvalue_reference_v) + { + return std::forward_as_tuple(std::forward(t)); + } + else + { + return std::make_tuple(std::forward(t)); + } + } + + template + static auto ensure_tuple(Tuple&& tuple) -> decltype(auto) + { + using Decay = std::decay_t; + if constexpr (meta::is_tuple_v || meta::is_pair_v) + { + return std::forward(tuple); + } + else + { + return forward_as_tuple(std::forward(tuple)); + } + } + } + template struct base_wrapper { @@ -42,19 +75,21 @@ namespace blt::iterator base_wrapper operator--(int) { - static_assert(meta::is_bidirectional_or_better_category_v, "Iterator must allow random access"); + static_assert(meta::is_bidirectional_or_better_category_v, + "Iterator must allow bidirectional access"); auto tmp = *this; --*this; return tmp; } - auto operator[](blt::ptrdiff_t n) const + auto operator[](ptrdiff_t n) const { - static_assert(meta::is_random_access_iterator_category_v, "Iterator must allow random access"); + static_assert(meta::is_random_access_iterator_category_v, + "Iterator must allow bidirectional access"); return *(*this + n); } - friend base_wrapper operator+(blt::ptrdiff_t n, const base_wrapper& a) + friend base_wrapper operator+(ptrdiff_t n, const base_wrapper& a) { return a + n; } @@ -95,9 +130,8 @@ namespace blt::iterator }; template - struct passthrough_wrapper : public base_wrapper + struct passthrough_wrapper : base_wrapper { - public: explicit passthrough_wrapper(Iter iter): iter(std::move(iter)) { } @@ -107,7 +141,7 @@ namespace blt::iterator return iter; } - friend blt::ptrdiff_t operator-(const passthrough_wrapper& a, const passthrough_wrapper& b) + friend ptrdiff_t operator-(const passthrough_wrapper& a, const passthrough_wrapper& b) { return a.base() - b.base(); } @@ -117,7 +151,7 @@ namespace blt::iterator }; template - struct passthrough_wrapper : public passthrough_wrapper + struct passthrough_wrapper : passthrough_wrapper { using passthrough_wrapper::passthrough_wrapper; @@ -215,6 +249,54 @@ namespace blt::iterator Pred func; }; + template + class const_wrapper : public deref_only_wrapper> + { + public: + using ref_return = meta::deref_return_t; + + using iterator_category = typename std::iterator_traits::iterator_category; + using value_type = std::conditional_t, std::remove_reference_t, ref_return>; + using difference_type = ptrdiff_t; + using pointer = const value_type*; + using reference = const value_type&; + + explicit const_wrapper(Iter iter): deref_only_wrapper(std::move(iter)) + { + } + + template + static auto make_const(T&& value) -> decltype(auto) + { + using Decay = std::decay_t; + if constexpr (std::is_lvalue_reference_v) + { + return const_cast(value); + } else + { + return static_cast(value); + } + } + + auto operator*() const + { + if constexpr (std::is_reference_v) + { + return const_cast(*this->iter); + } else if constexpr (meta::is_tuple_v || meta::is_pair_v) + { + return std::apply([](auto&&... args) + { + return std::tuple(args)))...>{make_const(std::forward(args))...}; + }, *this->iter); + } + else + { + return *this->iter; + } + } + }; + namespace impl { template @@ -382,6 +464,30 @@ namespace blt::iterator return enumerate_iterator_container{begin(), end(), static_cast(std::distance(begin(), end()))}; } + auto flatten() const + { + return iterator_container>{ + blt::iterator::flatten_wrapper{m_begin}, + blt::iterator::flatten_wrapper{m_end} + }; + } + + auto flatten_all() const + { + return iterator_container>{ + blt::iterator::flatten_wrapper{m_begin}, + blt::iterator::flatten_wrapper{m_end} + }; + } + + auto as_const() const + { + return iterator_container>{ + const_wrapper{m_begin}, + const_wrapper{m_end} + }; + } + template auto map(Func func) const { diff --git a/include/blt/iterator/enumerate.h b/include/blt/iterator/enumerate.h index 0a3ea12..7dd6293 100644 --- a/include/blt/iterator/enumerate.h +++ b/include/blt/iterator/enumerate.h @@ -20,120 +20,112 @@ #define BLT_ITERATOR_ENUMERATE_H #include -#include namespace blt { - namespace iterator { - /** - * struct which is returned by the enumerator. - * @tparam T type to store. - */ - template - struct enumerate_item - { - blt::size_t index; - T value; - }; - - template + template class enumerate_wrapper : public passthrough_wrapper> { - public: - enumerate_wrapper(blt::size_t index, Iter iter): passthrough_wrapper>(std::move(iter)), index(index) - {} - - using iterator_category = typename std::iterator_traits::iterator_category; - using value_type = enumerate_item>; - using difference_type = blt::ptrdiff_t; - using pointer = value_type; - using reference = value_type; - - enumerate_item> operator*() const - { - return {index, *this->iter}; - } - - enumerate_wrapper& operator++() - { - ++index; - ++this->iter; - return *this; - } - - enumerate_wrapper& operator--() - { - --index; - --this->iter; - return *this; - } - - friend enumerate_wrapper operator+(const enumerate_wrapper& a, blt::ptrdiff_t n) - { - static_assert(meta::is_random_access_iterator_v, "Iterator must allow random access"); - auto copy = a; - copy.index += n; - copy.iter = copy.iter + n; - return copy; - } - - friend enumerate_wrapper operator-(const enumerate_wrapper& a, blt::ptrdiff_t n) - { - static_assert(meta::is_random_access_iterator_v, "Iterator must allow random access"); - auto copy = a; - copy.index -= n; - copy.iter = copy.iter - n; - return copy; - } - - private: - blt::size_t index; + public: + enumerate_wrapper(const size_t index, Iter iter): passthrough_wrapper>(std::move(iter)), index(index) + { + } + + using iterator_category = typename std::iterator_traits::iterator_category; + using value_type = enumerate_item>; + using difference_type = blt::ptrdiff_t; + using pointer = value_type; + using reference = value_type; + + enumerate_item> operator*() const + { + return {index, *this->iter}; + } + + enumerate_wrapper& operator++() + { + ++index; + ++this->iter; + return *this; + } + + enumerate_wrapper& operator--() + { + --index; + --this->iter; + return *this; + } + + friend enumerate_wrapper operator+(const enumerate_wrapper& a, blt::ptrdiff_t n) + { + static_assert(meta::is_random_access_iterator_v, "Iterator must allow random access"); + auto copy = a; + copy.index += n; + copy.iter = copy.iter + n; + return copy; + } + + friend enumerate_wrapper operator-(const enumerate_wrapper& a, blt::ptrdiff_t n) + { + static_assert(meta::is_random_access_iterator_v, "Iterator must allow random access"); + auto copy = a; + copy.index -= n; + copy.iter = copy.iter - n; + return copy; + } + + private: + blt::size_t index; }; } - - template - class enumerate_iterator_container : public iterator::iterator_container> + + template + class enumerate_iterator_container : public blt::iterator::iterator_container> { - public: - using iterator::iterator_container>::iterator_container; - - enumerate_iterator_container(Iter begin, Iter end, blt::size_t size): - iterator::iterator_container>( - iterator::enumerate_wrapper{0, std::move(begin)}, iterator::enumerate_wrapper{size, std::move(end)}) - {} + public: + using blt::iterator::iterator_container>::iterator_container; + + enumerate_iterator_container(Iter begin, Iter end, blt::size_t size): + blt::iterator::iterator_container>( + blt::iterator::enumerate_wrapper{0, std::move(begin)}, blt::iterator::enumerate_wrapper{size, std::move(end)}) + { + } }; - - template + + template enumerate_iterator_container(Iter, Iter, blt::size_t) -> enumerate_iterator_container; - - template + + template static inline auto enumerate(T& container) { - return enumerate_iterator_container{container.begin(), container.end(), - static_cast(std::distance(container.begin(), container.end()))}; + return enumerate_iterator_container{ + container.begin(), container.end(), + static_cast(std::distance(container.begin(), container.end())) + }; } - - template + + template static inline auto enumerate(const T& container) { - return enumerate_iterator_container{container.begin(), container.end(), - static_cast(std::distance(container.begin(), container.end()))}; + return enumerate_iterator_container{ + container.begin(), container.end(), + static_cast(std::distance(container.begin(), container.end())) + }; } - - template - static inline auto enumerate(const T(& container)[size]) + + template + static inline auto enumerate(const T (&container)[size]) { return enumerate_iterator_container{&container[0], &container[size], size}; } - - template - static inline auto enumerate(T(& container)[size]) + + template + static inline auto enumerate(T (&container)[size]) { return enumerate_iterator_container{&container[0], &container[size], size}; } - } #endif //BLT_ITERATOR_ENUMERATE_H diff --git a/include/blt/iterator/flatten.h b/include/blt/iterator/flatten.h new file mode 100644 index 0000000..403bcf3 --- /dev/null +++ b/include/blt/iterator/flatten.h @@ -0,0 +1,89 @@ +#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_ITERATOR_FLATTEN_H +#define BLT_ITERATOR_FLATTEN_H + +#include +#include +#include + +namespace blt::iterator +{ + namespace detail + { + template + static auto flatten_recursive(Tuple&& tuple) -> decltype(auto) + { + using Decay = std::decay_t; + if constexpr (meta::is_tuple_v || meta::is_pair_v) + { + return std::apply([](auto&&... args) + { + return std::tuple_cat(flatten_recursive(std::forward(args))...); + }, std::forward(tuple)); + } + else + { + return forward_as_tuple(std::forward(tuple)); + } + } + + template + static auto flatten(Tuple&& tuple) -> decltype(auto) + { + return std::apply([](auto&&... args) + { + return std::tuple_cat(ensure_tuple(std::forward(args))...); + }, std::forward(tuple)); + } + } + + template + class flatten_wrapper : public deref_only_wrapper> + { + public: + using iterator_category = typename std::iterator_traits::iterator_category; + using value_type = std::conditional_t()))>, + std::remove_reference_t()))>>; + using difference_type = ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + explicit flatten_wrapper(Iter iter): + deref_only_wrapper(std::move(iter)) + { + } + + auto operator*() const -> decltype(auto) + { + if constexpr (Recursive) + { + return detail::flatten_recursive(*this->iter); + } + else + { + return detail::flatten(*this->iter); + } + } + }; +} + + +#endif //BLT_ITERATOR_FLATTEN_H diff --git a/include/blt/iterator/fwddecl.h b/include/blt/iterator/fwddecl.h index f0e80ea..50f18f1 100644 --- a/include/blt/iterator/fwddecl.h +++ b/include/blt/iterator/fwddecl.h @@ -19,6 +19,8 @@ #ifndef BLT_ITERATOR_FWDDECL_H #define BLT_ITERATOR_FWDDECL_H +#include + namespace blt { template @@ -33,7 +35,7 @@ namespace blt struct iterator_pair; template - struct enumerate_item; + using enumerate_item = std::pair; template class enumerate_wrapper; @@ -46,6 +48,12 @@ namespace blt template class filter_wrapper; + + template + class flatten_wrapper; + + template + class const_wrapper; namespace impl { diff --git a/include/blt/iterator/iterator.h b/include/blt/iterator/iterator.h index 48c1070..b758417 100644 --- a/include/blt/iterator/iterator.h +++ b/include/blt/iterator/iterator.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include diff --git a/include/blt/iterator/zip.h b/include/blt/iterator/zip.h index df26e43..bf7c069 100644 --- a/include/blt/iterator/zip.h +++ b/include/blt/iterator/zip.h @@ -21,6 +21,7 @@ #include #include +#include namespace blt { @@ -112,11 +113,11 @@ namespace blt class zip_iterator_container : public iterator::iterator_container> { public: - using iterator::iterator_container>::iterator_container; + using blt::iterator::iterator_container>::iterator_container; - explicit zip_iterator_container(iterator::iterator_pair... iterator_pairs): - iterator::iterator_container>(iterator::zip_wrapper{std::move(iterator_pairs.begin)...}, - iterator::zip_wrapper{std::move(iterator_pairs.end)...}) + explicit zip_iterator_container(blt::iterator::iterator_pair... iterator_pairs): + blt::iterator::iterator_container>(blt::iterator::zip_wrapper{std::move(iterator_pairs.begin)...}, + blt::iterator::zip_wrapper{std::move(iterator_pairs.end)...}) {} }; diff --git a/include/blt/logging/ansi.h b/include/blt/logging/ansi.h new file mode 100644 index 0000000..22b4cd9 --- /dev/null +++ b/include/blt/logging/ansi.h @@ -0,0 +1,353 @@ +#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_COLORS_H +#define BLT_LOGGING_COLORS_H + +#include +#include +#include +#include +#include + +#define BLT_ANSI_ESCAPE "\x1B" +#define BLT_ANSI_CSI BLT_ANSI_ESCAPE "[" +#define BLT_ANSI_DSC BLT_ANSI_ESCAPE "P" +#define BLT_ANSI_OSC BLT_ANSI_ESCAPE "]" + +// https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797 +namespace blt::logging::ansi +{ + namespace color + { + inline std::array reset_sequences = {0, 22, 22, 23, 24, 25, 26, 27, 28, 29}; + + enum class color_mode : u8 + { + RESET_ALL = 0, + BOLD = 1, + DIM = 2, + ITALIC = 3, + UNDERLINE = 4, + BLINK = 5, + REVERSE = 7, + HIDDEN = 8, + STRIKE_THROUGH = 9, }; + + enum class color8 : u8 + { + BLACK = 0, + RED = 1, + GREEN = 2, + YELLOW = 3, + BLUE = 4, + MAGENTA = 5, + CYAN = 6, + WHITE = 7, + DEFAULT = 9 + }; + + enum class color8_bright : u8 + { + BLACK = 0, + RED = 1, + GREEN = 2, + YELLOW = 3, + BLUE = 4, + MAGENTA = 5, + CYAN = 6, + WHITE = 7 + }; + + struct rgb_t + { + u8 r, g, b; + }; + + struct color256 + { + explicit color256(u8 index) : color(index) + {} + + color256(const u8 r, const u8 g, const u8 b) : color(rgb_t{r, g, b}) + { + if (r > 5) + throw std::invalid_argument("r must be between 0 and 5"); + if (g > 5) + throw std::invalid_argument("g must be between 0 and 5"); + if (b > 5) + throw std::invalid_argument("b must be between 0 and 5"); + } + + [[nodiscard]] u8 index() const + { + if (std::holds_alternative(color)) + return std::get(color); + const auto [r, g, b] = std::get(color); + return (r * 36) + (g * 6) + b + 16; + } + + private: + std::variant color; + }; + + struct color_rgb + { + color_rgb(const u8 r, const u8 g, const u8 b) : color(rgb_t{r, g, b}) + {} + + rgb_t color; + }; + + namespace detail + { + template + struct color_holder + { + using value_type = T; + + T color; + bool alt = false; + }; + + template + struct color_converter + {}; + + template <> + struct color_converter + { + static std::string to_string(const color_holder color) + { + return (color.alt ? "4" : "3") + std::to_string(static_cast(color.color)); + } + }; + + template <> + struct color_converter + { + static std::string to_string(const color_holder color) + { + return (color.alt ? "10" : "9") + std::to_string(static_cast(color.color)); + } + }; + + template <> + struct color_converter + { + static std::string to_string(const color_holder color) + { + return color.alt ? std::to_string(reset_sequences[static_cast(color.color)]) : std::to_string(static_cast(color.color)); + } + }; + + template <> + struct color_converter + { + static std::string to_string(const color_holder color) + { + return (color.alt ? "48;5;" : "38;5;") + std::to_string(color.color.index()); + } + }; + + template <> + struct color_converter + { + static std::string to_string(const color_holder color) + { + return (color.alt ? "48;2;" : "38;2;") + std::to_string(color.color.color.r) + ";" + std::to_string(color.color.color.g) + ";" + + std::to_string(color.color.color.b); + } + }; + + template + struct ensure_holder + { + static color_holder make(const T& color) + { + return color_holder{color, false}; + } + }; + + template + struct ensure_holder> + { + static color_holder make(const color_holder& color) + { + return color; + } + }; + + template + struct decay + { + using type = std::decay_t; + }; + + template + struct decay> + { + using type = std::decay_t; + }; + + template + using decay_t = typename decay::type; + } + + template + auto fg(const T& color) + { + return detail::color_holder{color, false}; + } + + template + auto bg(const T& color) + { + return detail::color_holder{color, true}; + } + + template + std::string build(const Args&... args) + { + std::string result = BLT_ANSI_CSI; + ((result += detail::color_converter>::to_string(detail::ensure_holder::make(args)), result += ';'), ...); + return result.substr(0, result.size() - 1) + "m"; + } + } + + namespace general + { + inline const std::string bell = "\x07"; + inline const std::string bs = "\x08"; + inline const std::string horizontal_tab = "\x09"; + inline const std::string linefeed = "\x0A"; + inline const std::string vertical_tab = "\x0B"; + inline const std::string form_feed = "\x0C"; + inline const std::string carriage_return = "\x0D"; + inline const std::string escape = BLT_ANSI_ESCAPE; + inline const std::string del = "\x7F"; + inline const std::string csi = BLT_ANSI_CSI; + inline const std::string dsc = BLT_ANSI_DSC; + inline const std::string osc = BLT_ANSI_OSC; + } + + namespace cursor + { + inline const std::string home = BLT_ANSI_CSI "H"; + inline const std::string lower_left_corner = BLT_ANSI_ESCAPE " F"; + inline const std::string hide_cursor = BLT_ANSI_CSI "?2 5l"; + inline const std::string show_cursor = BLT_ANSI_CSI "?2 5h"; + inline const std::string report_position = BLT_ANSI_CSI "6n"; + + template + std::string move_to(const i64 line, const i64 column) + { + if constexpr (UseH) + return std::string(BLT_ANSI_CSI) + std::to_string(line) + ";" + std::to_string(column) + "H"; + else + return std::string(BLT_ANSI_CSI) + std::to_string(line) + ";" + std::to_string(column) + "f"; + } + + inline std::string move_up(const i64 lines) + { + return std::string(BLT_ANSI_CSI) + std::to_string(lines) + "A"; + } + + inline std::string move_down(const i64 lines) + { + return std::string(BLT_ANSI_CSI) + std::to_string(lines) + "B"; + } + + inline std::string move_right(const i64 columns) + { + return std::string(BLT_ANSI_CSI) + std::to_string(columns) + "C"; + } + + inline std::string move_left(const i64 columns) + { + return std::string(BLT_ANSI_CSI) + std::to_string(columns) + "D"; + } + + inline std::string move_begin_down(const i64 lines) + { + return std::string(BLT_ANSI_CSI) + std::to_string(lines) + "E"; + } + + inline std::string move_begin_up(const i64 lines) + { + return std::string(BLT_ANSI_CSI) + std::to_string(lines) + "F"; + } + + inline std::string move_to(const i64 column) + { + return std::string(BLT_ANSI_CSI) + std::to_string(column) + "G"; + } + + inline const std::string request_cursor_position = BLT_ANSI_CSI "6n"; + inline const std::string move_up_one_line = BLT_ANSI_ESCAPE " M"; + inline const std::string save_cursor_position_dec = BLT_ANSI_ESCAPE " 7"; + inline const std::string restore_cursor_position_dec = BLT_ANSI_ESCAPE " 8"; + inline const std::string save_cursor_position_sco = BLT_ANSI_CSI "s"; + inline const std::string restore_cursor_position_sco = BLT_ANSI_CSI "u"; + } + + namespace scroll + { + inline std::string scroll_up(const int lines) + { + return std::string(BLT_ANSI_CSI) + std::to_string(lines) + "S"; + }; + } + + namespace erase + { + inline const std::string to_end_of_screen = BLT_ANSI_CSI "0J"; + inline const std::string from_begin_of_screen = BLT_ANSI_CSI "1J"; + inline const std::string entire_screen = BLT_ANSI_CSI "2J"; + inline const std::string saved_lines = BLT_ANSI_CSI "3J"; + inline const std::string to_end_of_line = BLT_ANSI_CSI "0K"; + inline const std::string from_begin_of_line = BLT_ANSI_CSI "1K"; + inline const std::string entire_line = BLT_ANSI_CSI "2K"; + } + + enum class mode : u8 + { + mono40x25_text = 0, + color40x25_text = 1, + mono80x25_text = 2, + color80x25_text = 3, + color320x200_4color_graphics = 4, + mono320x200_graphics = 5, + mono640x200_graphics = 6, + line_wrapping = 7, + color320x200_graphics = 13, + color640x200_16color_graphics = 14, + mono640x350_2color_graphics = 15, + color640x350_16color_graphics = 16, + mono640x480_2color_graphics = 17, + color640x480_16color_graphics = 18, + color320_200_256color_graphics = 19 + }; + + inline std::string use_mode(const mode mode) + { + return std::string(BLT_ANSI_CSI) + "=" + std::to_string(static_cast(mode)) + "h"; + } +} + +#endif //BLT_LOGGING_COLORS_H diff --git a/include/blt/logging/fmt_tokenizer.h b/include/blt/logging/fmt_tokenizer.h new file mode 100644 index 0000000..136994f --- /dev/null +++ b/include/blt/logging/fmt_tokenizer.h @@ -0,0 +1,188 @@ +#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 +#include + +namespace blt::logging +{ + enum class fmt_token_type : u8 + { + STRING, + NUMBER, + SPACE, + COLON, + DOT, + MINUS, + PLUS, + POUND, + LEFT_CHEVRON, + RIGHT_CHEVRON, + OPEN_BRACKET, + CLOSE_BRACKET, + CARET + }; + + enum class fmt_align_t : u8 + { + LEFT, + CENTER, + RIGHT + }; + + enum class fmt_sign_t : u8 + { + SPACE, + PLUS, + MINUS + }; + + enum class fmt_type_t : u8 + { + BINARY, // 'b' + CHAR, // 'c' + DECIMAL, // 'd' + OCTAL, // 'o' + HEX, // 'x' + HEX_FLOAT, // 'a' + EXPONENT, // 'e' + FIXED_POINT, // 'f' + GENERAL, // 'g' + TYPE, // 't' + UNSPECIFIED // default + }; + + struct fmt_spec_t + { + i64 arg_id = -1; + i64 width = -1; + i64 precision = -1; + fmt_type_t type = fmt_type_t::UNSPECIFIED; + fmt_sign_t sign = fmt_sign_t::MINUS; + fmt_align_t alignment = fmt_align_t::RIGHT; + std::optional prefix_char; + bool uppercase = false; + bool alternate_form = false; + }; + + struct fmt_token_t + { + fmt_token_type type; + std::string_view value; + }; + + class fmt_tokenizer_t + { + public: + explicit fmt_tokenizer_t() = default; + + static fmt_token_type get_type(char c); + + std::optional next(); + + std::vector tokenize(std::string_view fmt); + + private: + size_t m_pos = 0; + std::string_view m_fmt{}; + }; + + + class fmt_parser_t + { + public: + explicit fmt_parser_t(std::vector>& handlers): m_handlers(handlers) + { + } + + fmt_token_t& peek(const size_t offset) + { + return m_tokens[m_pos + offset]; + } + + fmt_token_t& peek() + { + return m_tokens[m_pos]; + } + + [[nodiscard]] bool has_next() const + { + 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++]; + } + + void consume() + { + ++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(); + std::string parse_arg_or_number(); + + void parse_fmt_spec(); + void parse_fmt_spec_fill(); + 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_fill(); + void parse_align(); + void parse_sign(); + void parse_form(); + void parse_width(); + void parse_precision(); + void parse_type(); + + size_t m_pos = 0; + std::vector m_tokens; + fmt_tokenizer_t m_tokenizer; + fmt_spec_t m_spec; + + std::vector>& m_handlers; + }; +} + +#endif //BLT_LOGGING_FMT_TOKENIZER_H diff --git a/include/blt/logging/fwddecl.h b/include/blt/logging/fwddecl.h new file mode 100644 index 0000000..47ecdd2 --- /dev/null +++ b/include/blt/logging/fwddecl.h @@ -0,0 +1,37 @@ +#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_FWDDECL_H +#define BLT_LOGGING_FWDDECL_H + +namespace blt::logging +{ + struct logger_t; + enum class fmt_token_type : u8; + enum class fmt_align_t : u8; + enum class fmt_sign_t : u8; + enum class fmt_type_t : u8; + struct fmt_spec_t; + struct fmt_token_t; + class fmt_tokenizer_t; + class fmt_parser_t; + + class injector_t; +} + +#endif //BLT_LOGGING_FWDDECL_H diff --git a/include/blt/logging/injector.h b/include/blt/logging/injector.h new file mode 100644 index 0000000..2d886ed --- /dev/null +++ b/include/blt/logging/injector.h @@ -0,0 +1,41 @@ +#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_INJECTOR_H +#define BLT_LOGGING_INJECTOR_H + +namespace blt::logging +{ + struct injector_output_t + { + std::string new_logging_output; + // should we continue processing the injector call chain? + bool should_continue = true; + // should we log the resulting string at the end of the injector call chain? If false for any injector, it becomes false for all injectors. + bool should_log = true; + }; + + class injector_t + { + public: + virtual ~injector_t() = default; + virtual injector_output_t inject(const std::string& input) = 0; + }; +} + +#endif //BLT_LOGGING_INJECTOR_H diff --git a/include/blt/logging/logging.h b/include/blt/logging/logging.h new file mode 100644 index 0000000..bc2e7a4 --- /dev/null +++ b/include/blt/logging/logging.h @@ -0,0 +1,286 @@ +#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_LOGGING_H +#define BLT_LOGGING_LOGGING_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace blt::logging +{ + struct logger_t + { + explicit logger_t(std::ostream& stream): m_stream(stream) + {} + + template + std::string log(std::string fmt, Args&&... args) + { + if (fmt.empty()) + return fmt; + auto sequence = std::make_integer_sequence{}; + m_arg_print_funcs.clear(); + m_arg_print_funcs.resize(sizeof...(Args)); + create_conv_funcs(sequence, std::forward(args)...); + compile(std::move(fmt)); + process_strings(); + return to_string(); + } + + [[nodiscard]] std::string to_string() const; + + private: + template + void create_conv_funcs(std::integer_sequence, Args&&... args) + { + ((handle_func(std::forward(args))), ...); + } + + template + void handle_func(const T& 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 (type.type != fmt_type_t::BINARY && static_cast(t) >= 0l) + stream << ' '; + } + break; + case fmt_sign_t::PLUS: + case fmt_sign_t::MINUS: + break; + } + switch (type.type) + { + case fmt_type_t::BINARY: + { + if constexpr (std::is_trivially_copyable_v) + { + // copy bytes of type + char buffer[sizeof(T)]; + std::memcpy(buffer, &t, sizeof(T)); + // display prefix + if (type.alternate_form) + stream << '0' << (type.uppercase ? 'B' : 'b'); + // print bytes + for (size_t i = 0; i < sizeof(T); ++i) + { + // print bits + for (size_t j = 0; j < 8; ++j) + stream << ((buffer[i] & (1 << j)) ? '1' : '0'); + // special seperator defined via sign (weird hack, change?) + if (type.sign == fmt_sign_t::SPACE && i != sizeof(T) - 1) + stream << ' '; + } + } else + { + if constexpr (blt::meta::is_streamable_v) + stream << t; + else + stream << "{INVALID TYPE}"; + } + break; + } + case fmt_type_t::CHAR: + if constexpr (std::is_arithmetic_v || std::is_convertible_v) + { + stream << static_cast(t); + } else + { + if constexpr (blt::meta::is_streamable_v) + stream << t; + else + stream << "{INVALID TYPE}"; + } + break; + case fmt_type_t::TYPE: + stream << blt::type_string(); + break; + default: + handle_type(stream, type); + if constexpr (blt::meta::is_streamable_v) + { + if constexpr (std::is_same_v || std::is_same_v) + stream << static_cast(t); + else if constexpr (std::is_same_v) + stream << static_cast(t); + else + stream << t; + } else + stream << "{INVALID TYPE}"; + } + }; + } + + [[nodiscard]] size_t find_ending_brace(size_t begin) const; + void setup_stream(const fmt_spec_t& spec) const; + std::string process_string(std::string_view str); + void process_strings(); + static void handle_type(std::ostream& stream, const fmt_spec_t& spec); + + static void exponential(std::ostream& stream); + static void fixed(std::ostream& stream); + + void compile(std::string fmt); + + std::optional> consume_to_next_fmt(); + + std::string m_fmt; + std::ostream& m_stream; + fmt_parser_t m_parser{m_arg_print_funcs}; + // 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; + }; + + void print(std::string str); + + void newline(); + + logger_t& get_global_logger(); + + logging_config_t& get_global_config(); + + std::ostream& get_local_stream(); + + void set_thread_name(const std::string& name); + + const std::string& get_thread_name(); + + template + void print(std::string fmt, Args&&... args) + { + auto& logger = get_global_logger(); + 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; + } + + template + void log(const log_level_t level, const char* file, const i32 line, std::string fmt, Args&&... args) + { + auto& logger = get_global_logger(); + const auto& config = get_global_config(); + std::string user_str = logger.log(std::move(fmt), std::forward(args)...); + if (!user_str.empty() && user_str.back() == '\n') + user_str.pop_back(); + if (level == log_level_t::NONE) + { + print(user_str); + newline(); + return; + } + auto log_fmt_str = config.generate(user_str, get_thread_name(), level, file, line); + if (log_fmt_str) + print(std::move(*log_fmt_str)); + } + + namespace detail + { + + } +} + +#if defined(__clang__) || defined(__llvm__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" +#endif + +#ifdef BLT_DISABLE_LOGGING +#define BLT_LOG(level, fmt, ...) + +#else +#define BLT_LOG(level, fmt, ...) blt::logging::log(level, __FILE__, __LINE__, fmt, ##__VA_ARGS__) + +#ifdef BLT_DISABLE_TRACE +#define BLT_TRACE(format, ...) +#else +#define BLT_TRACE(format, ...) BLT_LOG(blt::logging::log_level_t::TRACE, format, ##__VA_ARGS__) +#endif + +#ifdef BLT_DISABLE_DEBUG +#define BLT_DEBUG(format, ...) +#else +#define BLT_DEBUG(format, ...) BLT_LOG(blt::logging::log_level_t::DEBUG, format, ##__VA_ARGS__) +#endif + +#ifdef BLT_DISABLE_INFO +#define BLT_INFO(format, ...) +#else +#define BLT_INFO(format, ...) BLT_LOG(blt::logging::log_level_t::INFO, format, ##__VA_ARGS__) +#endif + +#ifdef BLT_DISABLE_WARN +#define BLT_WARN(format, ...) +#else +#define BLT_WARN(format, ...) BLT_LOG(blt::logging::log_level_t::WARN, format, ##__VA_ARGS__) +#endif + +#ifdef BLT_DISABLE_ERROR +#define BLT_ERROR(format, ...) +#else +#define BLT_ERROR(format, ...) BLT_LOG(blt::logging::log_level_t::ERROR, format, ##__VA_ARGS__) +#endif + +#ifdef BLT_DISABLE_FATAL +#define BLT_FATAL(format, ...) +#else +#define BLT_FATAL(format, ...) BLT_LOG(blt::logging::log_level_t::FATAL, format, ##__VA_ARGS__) +#endif + +#endif + +#if defined(__clang__) || defined(__llvm__) +#pragma clang diagnostic pop +#endif + +#endif // BLT_LOGGING_LOGGING_H diff --git a/include/blt/logging/logging_config.h b/include/blt/logging/logging_config.h new file mode 100644 index 0000000..8acf31b --- /dev/null +++ b/include/blt/logging/logging_config.h @@ -0,0 +1,243 @@ +#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_LOGGING_CONFIG_H +#define BLT_LOGGING_LOGGING_CONFIG_H + +#include +#include +#include +#include +#include +#include +#include + +namespace blt::logging +{ + namespace tags + { + // Current Year + inline constexpr auto YEAR = "{YEAR}"; + // Current Month + inline constexpr auto MONTH = "{MONTH}"; + // Current Day + inline constexpr auto DAY = "{DAY}"; + // Current Hour + inline constexpr auto HOUR = "{HOUR}"; + // Current Minute + inline constexpr auto MINUTE = "{MINUTE}"; + // Current Second + inline constexpr auto SECOND = "{SECOND}"; + // Current Millisecond + inline constexpr auto MILLISECOND = "{MS}"; + // Current Nanosecond + inline constexpr auto NANOSECOND = "{NS}"; + // Current Unix time in milliseconds + inline constexpr auto UNIX_TIME = "{UNIX}"; + // Current Unix time in nanosecond + inline constexpr auto UNIX_TIME_NANO = "{UNIX_NANO}"; + // Formatted ISO year-month-day in a single variable + inline constexpr auto ISO_YEAR = "{ISO_YEAR}"; + // Formatted hour:minute:second in a single variable + inline constexpr auto TIME = "{TIME}"; + // Formatted year-month-day hour:minute:second in a single variable + inline constexpr auto FULL_TIME = "{FULL_TIME}"; + // Color of the current log level, empty string if use_color = false + inline constexpr auto LOG_COLOR = "{LC}"; + // Color of the error color, empty string if use_color = false + inline constexpr auto ERROR_COLOR = "{EC}"; + // Empty is use_color = false or if log level is not an error. Otherwise, {EC} + inline constexpr auto CONDITIONAL_ERROR_COLOR = "{CEC}"; + // Resets all ANSI sequences + inline constexpr auto RESET = "{RESET}"; + // Current log level + inline constexpr auto LOG_LEVEL = "{LL}"; + // Current thread name. Requires you to manually set the thread name using blt::logging::set_thread_name() from that thread. + inline constexpr auto THREAD_NAME = "{TN}"; + // Current file from where the log call was invoked. + inline constexpr auto FILE = "{FILE}"; + // Current line from where the log call was invoked + inline constexpr auto LINE = "{LINE}"; + // User string input, formatted with provided args + inline constexpr auto STR = "{STR}"; + + namespace detail + { + enum class log_tag_token_t : u8 + { + YEAR, + MONTH, + DAY, + HOUR, + MINUTE, + SECOND, + MS, + NS, + UNIX, + UNIX_NANO, + ISO_YEAR, + TIME, + FULL_TIME, + LC, + EC, + CEC, + RESET, + LL, + TN, + FILE, + LINE, + STR, + // token used to describe that a non-format token should be consumed. aka a normal string from the file. + CONTENT + }; + } + } + + enum class log_level_t : u8 + { + TRACE, + DEBUG, + INFO, + WARN, + ERROR, + FATAL, + NONE + }; + + inline constexpr size_t LOG_LEVEL_COUNT = 6; + + class logging_config_t + { + friend logger_t; + + public: + logging_config_t() + { + compile(); + } + + void compile(); + + logging_config_t& add_log_output(fs::writer_t& writer) + { + m_log_outputs.push_back(&writer); + return *this; + } + + logging_config_t& add_injector(injector_t& injector) + { + m_injectors.push_back(&injector); + return *this; + } + + logging_config_t& set_log_format(std::string format) + { + m_log_format = std::move(format); + compile(); + return *this; + } + + logging_config_t& set_error_color(std::string color) + { + m_error_color = std::move(color); + compile(); + return *this; + } + + logging_config_t& set_log_level_colors(std::array colors) + { + m_log_level_colors = std::move(colors); + compile(); + return *this; + } + + logging_config_t& set_log_level_names(std::array names) + { + m_log_level_names = std::move(names); + return *this; + } + + logging_config_t& set_level(const log_level_t level) + { + this->m_level = level; + return *this; + } + + logging_config_t& set_use_color(const bool use_color) + { + this->m_use_color = use_color; + compile(); + return *this; + } + + logging_config_t& set_print_full_name(const bool print_full_name) + { + this->m_print_full_name = print_full_name; + return *this; + } + + logging_config_t& set_ensure_alignment(const bool ensure_alignment) + { + this->m_ensure_alignment = ensure_alignment; + return *this; + } + + [[nodiscard]] std::pair&, const std::vector&> get_log_tag_tokens() const + { + return {m_log_tag_tokens, m_log_tag_content}; + } + + std::optional generate(const std::string& user_str, const std::string& thread_name, log_level_t level, const char* file, + i32 line) const; + + [[nodiscard]] const std::vector& get_injectors() const + { + return m_injectors; + } + + private: + std::vector m_injectors; + // wrappers for streams exist in blt/fs/stream_wrappers.h + std::vector m_log_outputs = get_default_log_outputs(); + std::string m_log_format = get_default_log_format(); + std::string m_error_color = get_default_error_color(); + std::array m_log_level_colors = get_default_log_level_colors(); + std::array m_log_level_names = get_default_log_level_names(); + log_level_t m_level = log_level_t::TRACE; + + bool m_use_color = true; + // if true prints the whole path to the file (eg /home/user/.../.../project/src/source.cpp:line#) + bool m_print_full_name = false; + // this will attempt to use the maximum possible size for each printed element, then align to that. + // This creates output where the user message always starts at the same column. + bool m_ensure_alignment = true; + + size_t m_longest_name_length = 0; + + std::vector m_log_tag_content; + std::vector m_log_tag_tokens; + + static std::string get_default_log_format(); + static std::vector get_default_log_outputs(); + static std::array get_default_log_level_colors(); + static std::array get_default_log_level_names(); + static std::string get_default_error_color(); + }; +} + +#endif //BLT_LOGGING_LOGGING_CONFIG_H diff --git a/include/blt/logging/status.h b/include/blt/logging/status.h new file mode 100644 index 0000000..b6d2fe2 --- /dev/null +++ b/include/blt/logging/status.h @@ -0,0 +1,105 @@ +#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_STATUS_H +#define BLT_LOGGING_STATUS_H + +#include +#include +#include +#include +#include + +namespace blt::logging +{ + class status_bar_t; + + class status_item_t + { + public: + virtual ~status_item_t() = default; + + void assign(status_bar_t* bar) + { + m_status = bar; + } + + [[nodiscard]] virtual i32 lines_used() const + { + return 1; + } + + [[nodiscard]] virtual std::string print(vec2i screen_size, i32 max_printed_length) const = 0; + protected: + status_bar_t* m_status = nullptr; + }; + + class status_progress_bar_t final : public status_item_t + { + public: + [[nodiscard]] std::string print(vec2i screen_size, i32 max_printed_length) const override; + + void set_progress(double progress); + + void add_progress(const double progress) + { + set_progress(get_progress() + progress); + } + + [[nodiscard]] double get_progress() const + { + return m_progress; + } + private: + double m_progress = 0.0; + }; + + class status_bar_t final : public injector_t + { + public: + explicit status_bar_t(); + + injector_output_t inject(const std::string& input) override; + + status_bar_t& add(status_item_t& item) + { + item.assign(this); + m_status_items.push_back(&item); + compute_size(); + return *this; + } + + void redraw() const; + + ~status_bar_t() override; + private: + void compute_size(); + + std::vector m_status_items; + i32 m_status_size = 0; + i32 m_max_printed_length = 0; + vec2i m_screen_size; + vec2i m_last_log_position; + vec2i m_begin_position; + + std::mutex m_print_mutex; + }; + +} + +#endif //BLT_LOGGING_STATUS_H diff --git a/include/blt/math/aabb.h b/include/blt/math/aabb.h new file mode 100644 index 0000000..e0c94c4 --- /dev/null +++ b/include/blt/math/aabb.h @@ -0,0 +1,170 @@ +#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_MATH_AABB_H +#define BLT_MATH_AABB_H + +#include +#include +#include + +namespace blt +{ + // yes I could use the vector (see tower defense game commit log for this) + // this feels nicer + template + class axis_t + { + public: + axis_t(const T min, const T max): m_min(min), m_max(max) + {} + + [[nodiscard]] bool intersects(const T p) const + { + return p >= m_min && p <= m_max; + } + + template + [[nodiscard]] bool intersects(const axis_t& other) const + { + return static_cast(other.m_min) <= m_max && static_cast(other.m_max) >= m_min; + } + + [[nodiscard]] T min() const + { + return m_min; + } + + [[nodiscard]] T max() const + { + return m_max; + } + + [[nodiscard]] T length() const + { + return m_max - m_min; + } + + private: + T m_min, m_max; + }; + + namespace detail + { + template + class axis_aligned_bounding_box_base_t + { + public: + [[nodiscard]] vec get_center() const + { + vec min; + for (u32 i = 0; i < Axis; i++) + min[i] = m_axes[i].min(); + const auto center = get_size() / 2.0f; + return min + center; + } + + [[nodiscard]] vec get_size() const + { + vec size; + for (u32 i = 0; i < Axis; i++) + size[i] = m_axes[i].length(); + return size; + } + + template + [[nodiscard]] bool intersects(const axis_aligned_bounding_box_base_t& other) const + { + for (u32 i = 0; i < Axis; i++) + if (!m_axes[i].intersects(other.m_axes[i])) + return false; + return true; + } + + template + [[nodiscard]] bool intersects(const vec& point) const + { + for (u32 i = 0; i < Axis; i++) + if (!m_axes[i].intersects(point[i])) + return false; + return true; + } + + axis_t& operator[](u32 i) + { + return m_axes[i]; + } + + axis_t& axis(u32 i) + { + if (i >= Axis) + throw std::out_of_range("Axis index out of range"); + return m_axes[i]; + } + + protected: + std::array, Axis> m_axes; + }; + } + + template + class axis_aligned_bounding_box_t : public detail::axis_aligned_bounding_box_base_t + { + public: + using detail::axis_aligned_bounding_box_base_t::axis_aligned_bounding_box_base_t; + }; + + template + class axis_aligned_bounding_box_t<2, T> : public detail::axis_aligned_bounding_box_base_t<2, T> + { + public: + using detail::axis_aligned_bounding_box_base_t<2, T>::axis_aligned_bounding_box_base_t; + + [[nodiscard]] vec2 min() const + { + return {this->m_axes[0].min(), this->m_axes[1].min()}; + } + + [[nodiscard]] vec2 max() const + { + return {this->m_axes[0].max(), this->m_axes[1].max()}; + } + }; + + template + class axis_aligned_bounding_box_t<3, T> : public detail::axis_aligned_bounding_box_base_t<3, T> + { + public: + using detail::axis_aligned_bounding_box_base_t<2, T>::axis_aligned_bounding_box_base_t; + + [[nodiscard]] vec3 min() const + { + return {this->m_axes[0].min(), this->m_axes[1].min(), this->m_axes[2].min()}; + } + + [[nodiscard]] vec3s max() const + { + return {this->m_axes[0].max(), this->m_axes[1].max(), this->m_axes[2].max()}; + } + }; + + using aabb_2d_t = axis_aligned_bounding_box_t<2, float>; + using aabb_3d_t = axis_aligned_bounding_box_t<3, float>; +} + +#endif //BLT_MATH_AABB_H diff --git a/include/blt/math/bounding_box.h b/include/blt/math/bounding_box.h new file mode 100644 index 0000000..24cbcb0 --- /dev/null +++ b/include/blt/math/bounding_box.h @@ -0,0 +1,28 @@ +#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_MATH_BOUNDING_BOX_H +#define BLT_MATH_BOUNDING_BOX_H + +#include + +namespace blt { + +} + +#endif //BLT_MATH_BOUNDING_BOX_H diff --git a/include/blt/math/log_util.h b/include/blt/math/log_util.h index fb2510c..6b20836 100644 --- a/include/blt/math/log_util.h +++ b/include/blt/math/log_util.h @@ -9,7 +9,7 @@ #include #include -#include +#include #include #include "blt/std/string.h" diff --git a/include/blt/math/vectors.h b/include/blt/math/vectors.h index 7ab4853..81107db 100644 --- a/include/blt/math/vectors.h +++ b/include/blt/math/vectors.h @@ -21,9 +21,9 @@ namespace blt constexpr float EPSILON = std::numeric_limits::epsilon(); - static inline constexpr bool f_equal(float v1, float v2) + static constexpr bool f_equal(const float v1, const float v2, const float range = 1) { - return v1 >= v2 - EPSILON && v1 <= v2 + EPSILON; + return v1 >= v2 - (EPSILON * range) && v1 <= v2 + (EPSILON * range); } template @@ -107,6 +107,11 @@ namespace blt } } + [[nodiscard]] const std::array& to_array() const + { + return elements; + } + [[nodiscard]] constexpr inline T x() const { return elements[0]; @@ -423,6 +428,50 @@ namespace blt return true; } + template + constexpr bool operator>=(const vec& left, const vec& right) + { + for (u32 i = 0; i < size; i++) + { + if (left[i] < right[i]) + return false; + } + return true; + } + + template + constexpr bool operator>(const vec& left, const vec& right) + { + for (u32 i = 0; i < size; i++) + { + if (left[i] <= right[i]) + return false; + } + return true; + } + + template + constexpr bool operator<(const vec& left, const vec& right) + { + for (u32 i = 0; i < size; i++) + { + if (left[i] >= right[i]) + return false; + } + return true; + } + + template + constexpr bool operator<=(const vec& left, const vec& right) + { + for (u32 i = 0; i < size; i++) + { + if (left[i] > right[i]) + return false; + } + return true; + } + template inline constexpr bool operator!=(const vec& left, const vec& right) { diff --git a/include/blt/meta/is_streamable.h b/include/blt/meta/is_streamable.h new file mode 100644 index 0000000..58bea5a --- /dev/null +++ b/include/blt/meta/is_streamable.h @@ -0,0 +1,51 @@ +#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_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 + template + class is_streamable + { + private: + template + static auto test(int) -> decltype(std::declval() << std::declval(), std::true_type()) + { + return std::declval(); + } + + template + static auto test(...) -> std::false_type + { + return std::declval(); + } + + public: + static constexpr bool value = decltype(test(0))::value; + }; + + template + inline constexpr bool is_streamable_v = is_streamable::value; +} + +#endif //BLT_META_IS_STREAMABLE_H diff --git a/include/blt/meta/meta.h b/include/blt/meta/meta.h index fe502ca..82c74e7 100644 --- a/include/blt/meta/meta.h +++ b/include/blt/meta/meta.h @@ -23,6 +23,7 @@ #include #include #include +#include namespace blt::meta { @@ -71,30 +72,6 @@ namespace blt::meta template lambda_helper(Lambda) -> lambda_helper; - // https://stackoverflow.com/questions/66397071/is-it-possible-to-check-if-overloaded-operator-for-type-or-class-exists - template - class is_streamable - { - private: - template - static auto test(int) -> decltype(std::declval() << std::declval(), std::true_type()) - { - return std::declval(); - } - - template - static auto test(...) -> std::false_type - { - return std::declval(); - } - - public: - static constexpr bool value = decltype(test(0))::value; - }; - - template - inline constexpr bool is_streamable_v = is_streamable::value; - template struct arrow_return { @@ -114,7 +91,7 @@ namespace blt::meta template struct deref_return { - using type = typename std::invoke_result_t; + using type = std::invoke_result_t; }; template diff --git a/include/blt/meta/type_traits.h b/include/blt/meta/type_traits.h new file mode 100644 index 0000000..ae4b731 --- /dev/null +++ b/include/blt/meta/type_traits.h @@ -0,0 +1,73 @@ +#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_META_TYPE_TRAITS_H +#define BLT_META_TYPE_TRAITS_H + +#include +#include + +namespace blt::meta +{ + namespace detail + { + template + void empty_apply_function(Args...) + { + + } + + inline auto lambda = [](auto...) + { + }; + } + + template + using remove_cvref_t = std::remove_volatile_t>>; + + template + using add_const_ref_t = std::conditional_t, const std::remove_reference_t&, const U>; + + template + struct is_tuple : std::false_type + { + }; + + template + struct is_tuple> : std::true_type + { + }; + + template + struct is_pair : std::false_type + { + }; + + template + struct is_pair> : std::true_type + { + }; + + template + static constexpr bool is_tuple_v = is_tuple::value; + + template + static constexpr bool is_pair_v = is_pair::value; +} + +#endif // BLT_META_TYPE_TRAITS_H diff --git a/include/blt/parse/argparse_v2.h b/include/blt/parse/argparse_v2.h new file mode 100644 index 0000000..f44bf4e --- /dev/null +++ b/include/blt/parse/argparse_v2.h @@ -0,0 +1,811 @@ +#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_PARSE_ARGPARSE_V2_H +#define BLT_PARSE_ARGPARSE_V2_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace blt::argparse +{ + class argument_string_t; + class argument_consumer_t; + class argument_parser_t; + class argument_subparser_t; + class argument_builder_t; + class argument_storage_t; + class argument_positional_storage_t; + + enum class action_t + { + STORE, + STORE_CONST, + STORE_TRUE, + STORE_FALSE, + APPEND, + APPEND_CONST, + EXTEND, + COUNT, + HELP, + VERSION + }; + + enum class nargs_t + { + IF_POSSIBLE, + ALL, + ALL_AT_LEAST_ONE + }; + + using nargs_v = std::variant; + + namespace detail + { + class bad_flag final : public std::runtime_error + { + public: + explicit bad_flag(const std::string& message): std::runtime_error(message) + { + } + }; + + class bad_positional final : public std::runtime_error + { + public: + explicit bad_positional(const std::string& message): std::runtime_error(message) + { + } + }; + + class missing_argument_error final : public std::runtime_error + { + public: + explicit missing_argument_error(const std::string& message): std::runtime_error(message) + { + } + }; + + class missing_value_error final : public std::runtime_error + { + public: + explicit missing_value_error(const std::string& message): std::runtime_error(message) + { + } + }; + + class type_error final : public std::runtime_error + { + public: + explicit type_error(const std::string& message): std::runtime_error(message) + { + } + }; + + class unexpected_argument_error final : public std::runtime_error + { + public: + explicit unexpected_argument_error(const std::string& message): std::runtime_error(message) + { + } + }; + + class bad_choice_error final : public std::runtime_error + { + public: + explicit bad_choice_error(const std::string& message): std::runtime_error(message) + { + } + }; + + class subparse_error final : public std::exception + { + public: + explicit subparse_error(const std::string_view found_string, std::vector> allowed_strings): + m_found_string(found_string), + m_allowed_strings(std::move(allowed_strings)) + { + } + + [[nodiscard]] const std::vector>& get_allowed_strings() const + { + return m_allowed_strings; + } + + [[nodiscard]] std::string_view get_found_string() const + { + return m_found_string; + } + + [[nodiscard]] std::string error_string() const; + + [[nodiscard]] const char* what() const noexcept override + { + m_error_string = error_string(); + return m_error_string.c_str(); + } + + private: + mutable std::string m_error_string; + std::string_view m_found_string; + std::vector> m_allowed_strings; + }; + + template + struct arg_data_helper_t + { + using variant_t = std::variant...>; + using arg_t = meta::arg_helper; + using arg_vec_t = meta::arg_helper...>; + + template + constexpr static bool is_type_stored_v = std::disjunction_v...>; + + template + static auto make_visitor(const DefaultPrimitiveAction& primitive_action, const DefaultListAction& list_action) + { + return lambda_visitor{ + ([&primitive_action](Args& arg) + { + return primitive_action(arg); + })..., + ([&list_action](std::vector& arg_vec) + { + return list_action(arg_vec); + })... + }; + } + }; + + using arg_meta_type_helper_t = arg_data_helper_t; + using arg_data_t = arg_meta_type_helper_t::variant_t; + + template + struct arg_string_converter_t + { + static T convert(const std::string_view value) + { + static_assert(std::is_arithmetic_v || std::is_same_v || std::is_same_v, + "Type must be arithmetic, string_view or string!"); + const std::string temp{value}; + + if constexpr (std::is_same_v) + { + return std::stof(temp); + } + else if constexpr (std::is_same_v) + { + return std::stod(temp); + } + else if constexpr (std::is_unsigned_v) + { + return static_cast(std::stoull(temp)); + } + else if constexpr (std::is_signed_v) + { + return static_cast(std::stoll(temp)); + } + else if constexpr (std::is_same_v) + { + return value; + } + else if constexpr (std::is_same_v) + { + return std::string(value); + } + BLT_UNREACHABLE; + } + }; + + template + auto ensure_is_string(T&& t) + { + using NO_REF = meta::remove_cvref_t; + if constexpr (std::is_same_v) + return t ? "true" : "false"; + else if constexpr (std::is_arithmetic_v && !(std::is_same_v + || std::is_same_v || std::is_same_v)) + return std::to_string(std::forward(t)); + else + return std::forward(t); + } + + void test(); + } + + class argument_string_t + { + public: + explicit argument_string_t(const std::string_view input, const hashset_t& allowed_flag_prefix): m_argument(input), + m_allowed_flag_prefix(&allowed_flag_prefix) + { + process_argument(); + } + + + [[nodiscard]] std::string_view get_flag() const + { + return m_flag_section; + } + + [[nodiscard]] std::string_view get_name() const + { + return m_name_section; + } + + [[nodiscard]] std::string_view value() const + { + return get_name(); + } + + [[nodiscard]] bool is_flag() const + { + return !m_flag_section.empty(); + } + + [[nodiscard]] std::string_view get_argument() const + { + return m_argument; + } + + private: + /** + * This function takes the command line argument represented by this class, + * stored in m_argument and converts into flag side and name side arguments. + * + * What this means is in the case of a flag provided, for example passing --foo or --bar, this function will split the argument into + * + * m_flag_section = "--" + * m_name_section = "foo" || "bar" + * + * If the argument is purely positional, meaning there is no flag prefix, + * this function will do nothing and m_flag_section will be true for .empty() + * + * For example, if you provide res/some/folder/or/file as a command line positional argument, + * this function will create the following internal state: + * + * m_flag_section = "" + * m_flag_section.empty() == true + * m_name_section = "res/some/folder/or/file" + * + * m_argument is not modified by this function + */ + void process_argument() + { + // user provides a list of allowed prefix characters to argument_parser_t, which is then provided to this class at construction time + // it is not the job of this class to validate flag prefixes beyond this. // TODO: requiring this pointer is a code smell. + size_t start = 0; + for (; start < m_argument.size() && m_allowed_flag_prefix->contains(m_argument[start]); start++) + { + } + + m_flag_section = {m_argument.data(), start}; + m_name_section = {m_argument.data() + start, m_argument.size() - start}; + } + + std::string_view m_argument; + std::string_view m_flag_section; + std::string_view m_name_section; + const hashset_t* m_allowed_flag_prefix; + }; + + class argument_consumer_t + { + public: + explicit argument_consumer_t(const span& args): m_absolute_begin(args.data()), m_begin(args.data() + 1), + m_end(args.data() + args.size()) + { + BLT_ASSERT(!args.empty() && + "Argument consumer must have at least one argument allocated to it. First argument is always assumed to be program"); + } + + [[nodiscard]] const argument_string_t& absolute_first() const + { + return *m_absolute_begin; + } + + [[nodiscard]] argument_string_t peek(const i32 offset = 0) const + { + return *(m_begin + offset); + } + + [[nodiscard]] argument_string_t r_peek(const i32 offset = 0) const + { + return *(m_end - 1 - offset); + } + + argument_string_t consume() + { + return *(m_begin++); + } + + argument_string_t r_consume() + { + return *(--m_end); + } + + [[nodiscard]] i32 remaining() const + { + return static_cast(size()); + } + + [[nodiscard]] bool can_consume(const i32 amount = 0) const + { + return amount < remaining(); + } + + private: + [[nodiscard]] ptrdiff_t size() const + { + return m_end - m_begin; + } + + argument_string_t* m_absolute_begin; + argument_string_t* m_begin; + argument_string_t* m_end; + }; + + class argument_storage_t + { + friend argument_parser_t; + friend argument_subparser_t; + friend argument_builder_t; + + public: + template + [[nodiscard]] const T& get(const std::string_view key) const + { + return std::get(m_data.at(key)); + } + + [[nodiscard]] const std::string& get(const std::string_view key) const + { + return std::get(m_data.at(key)); + } + + [[nodiscard]] bool contains(const std::string_view key) const + { + return m_data.find(key) != m_data.end(); + } + + [[nodiscard]] size_t size() const + { + return m_data.size(); + } + + private: + void add(const argument_storage_t& values) + { + for (const auto& value : values.m_data) + m_data.insert(value); + } + + hashmap_t m_data; + }; + + class argument_builder_t + { + friend argument_parser_t; + + public: + argument_builder_t() + { + m_dest_func = [](const std::string_view dest, argument_storage_t& storage, std::string_view value) + { + storage.m_data.emplace(std::string{dest}, std::string{value}); + }; + m_dest_vec_func = [](const std::string_view dest, argument_storage_t& storage, const std::vector& values) + { + storage.m_data.emplace(std::string{dest}, values); + }; + } + + template + argument_builder_t& as_type() + { + static_assert(detail::arg_data_helper_t::template is_type_stored_v, "Type is not valid to be stored/converted as an argument"); + m_dest_func = [](const std::string_view dest, argument_storage_t& storage, std::string_view value) + { + storage.m_data.emplace(std::string{dest}, detail::arg_string_converter_t::convert(value)); + }; + m_dest_vec_func = [](const std::string_view dest, argument_storage_t& storage, const std::vector& values) + { + if (storage.m_data.contains(dest)) + { + auto& data = storage.m_data[dest]; + if (!std::holds_alternative>(data)) + throw detail::type_error("Invalid type conversion. Trying to add type " + blt::type_string() + + " but this does not match existing type index '" + std::to_string(data.index()) + "'!"); + auto& converted_values = std::get>(data); + for (const auto& value : values) + converted_values.push_back(detail::arg_string_converter_t::convert(value)); + } + else + { + std::vector converted_values; + for (const auto& value : values) + converted_values.push_back(detail::arg_string_converter_t::convert(value)); + storage.m_data.emplace(std::string{dest}, std::move(converted_values)); + } + }; + return *this; + } + + argument_builder_t& make_flag() + { + return set_action(action_t::STORE_TRUE); + } + + argument_builder_t& set_action(action_t action); + + argument_builder_t& set_required(const bool required) + { + m_required = required; + return *this; + } + + argument_builder_t& set_nargs(const nargs_v nargs) + { + m_nargs = nargs; + return *this; + } + + argument_builder_t& set_metavar(const std::string& metavar) + { + m_metavar = metavar; + return *this; + } + + argument_builder_t& set_help(const std::string& help) + { + m_help = help; + return *this; + } + + argument_builder_t& set_choices(const std::vector& choices) + { + m_choices = hashset_t{}; + for (const auto& choice : choices) + m_choices->emplace(choice); + return *this; + } + + argument_builder_t& set_choices(const hashset_t& choices) + { + m_choices = choices; + return *this; + } + + template + argument_builder_t& set_choices(Args&&... args) + { + m_choices = hashset_t{}; + ((m_choices->emplace(detail::ensure_is_string(std::forward(args)))), ...); + return *this; + } + + argument_builder_t& set_default(const detail::arg_data_t& default_value) + { + m_default_value = default_value; + return *this; + } + + argument_builder_t& set_const(const detail::arg_data_t& const_value) + { + m_const_value = const_value; + return *this; + } + + argument_builder_t& set_dest(const std::string_view& dest) + { + m_dest = dest; + return *this; + } + + private: + action_t m_action = action_t::STORE; + bool m_required = false; // do we require this argument to be provided as an argument? + nargs_v m_nargs = 1; // number of arguments to consume + std::optional m_metavar; // variable name to be used in the help string + std::optional m_help; // help string to be used in the help string + std::optional> m_choices; // optional allowed choices for this argument + std::optional m_default_value; + std::optional m_const_value; + std::optional m_dest; + // dest, storage, value input + std::function m_dest_func; + // dest, storage, value input + std::function& values)> m_dest_vec_func; + }; + + class argument_positional_storage_t + { + public: + explicit argument_positional_storage_t(std::vector> storage): positional_arguments( + std::move(storage)) + { + } + + argument_builder_t& peek() + { + return positional_arguments[current_positional].second; + } + + argument_builder_t& next() + { + return positional_arguments[current_positional++].second; + } + + [[nodiscard]] bool has_positional() const + { + return current_positional < positional_arguments.size(); + } + + [[nodiscard]] auto remaining() const + { + return iterate(positional_arguments).skip(current_positional); + } + + private: + std::vector> positional_arguments; + size_t current_positional = 0; + }; + + class argument_parser_t + { + friend argument_subparser_t; + explicit argument_parser_t(const argument_subparser_t* parent): m_parent(parent) + { + } + public: + explicit argument_parser_t(const std::optional description = {}, const std::optional epilogue = {}, + const std::optional version = {}, + const std::optional usage = {}, const std::optional name = {}): + m_name(name), m_usage(usage), m_description(description), m_epilogue(epilogue), m_version(version) + { + } + + template + argument_builder_t& add_flag(const std::string_view arg, Aliases... aliases) + { + static_assert( + std::conjunction_v, std::is_constructible< + std::string, Aliases>>...>, + "Arguments must be of type string_view, convertible to string_view or be string_view constructable"); + m_argument_builders.emplace_back(std::make_unique()); + m_argument_builders.back()->set_dest(arg); + m_flag_arguments.emplace(arg, m_argument_builders.back().get()); + (m_flag_arguments.emplace(aliases, m_argument_builders.back().get()), ...); + return *m_argument_builders.back().get(); + } + + argument_builder_t& add_positional(const std::string_view arg) + { + auto& b = m_positional_arguments.emplace_back(arg, argument_builder_t{}).second; + b.set_dest(std::string{arg}); + b.set_required(true); + b.set_nargs(1); + return b; + } + + argument_subparser_t* add_subparser(std::string_view dest); + + argument_parser_t& with_help() + { + add_flag("--help", "-h").set_action(action_t::HELP).set_help("Show this help menu and exit"); + return *this; + } + + argument_parser_t& with_version() + { + add_flag("--version").set_action(action_t::VERSION); + return *this; + } + + argument_storage_t parse(argument_consumer_t& consumer); // NOLINT + + argument_storage_t parse(const std::vector& args) + { + std::vector arg_strings; + arg_strings.reserve(args.size()); + for (const auto& arg : args) + arg_strings.emplace_back(arg, allowed_flag_prefixes); + argument_consumer_t consumer{arg_strings}; + return parse(consumer); + } + + argument_storage_t parse(const std::vector& args) + { + std::vector arg_strings; + arg_strings.reserve(args.size()); + for (const auto& arg : args) + arg_strings.emplace_back(arg, allowed_flag_prefixes); + argument_consumer_t consumer{arg_strings}; + return parse(consumer); + } + + argument_storage_t parse(const int argc, const char** argv) + { + std::vector arg_strings; + arg_strings.reserve(argc); + for (int i = 0; i < argc; ++i) + arg_strings.emplace_back(argv[i], allowed_flag_prefixes); + argument_consumer_t consumer{arg_strings}; + return parse(consumer); + } + + void print_help(); + + void print_usage(); + + void print_version() const; + + argument_parser_t& set_name(const std::optional& name) + { + m_name = name; + return *this; + } + + argument_parser_t& set_usage(const std::optional& usage) + { + m_usage = usage; + return *this; + } + + [[nodiscard]] const std::optional& get_usage() const + { + return m_usage; + } + + argument_parser_t& set_description(const std::optional& description) + { + m_description = description; + return *this; + } + + [[nodiscard]] const std::optional& get_description() const + { + return m_description; + } + + argument_parser_t& set_epilogue(const std::optional& epilogue) + { + m_epilogue = epilogue; + return *this; + } + + [[nodiscard]] const std::optional& get_epilogue() const + { + return m_epilogue; + } + + argument_parser_t& set_version(const std::optional& version) + { + m_epilogue = version; + return *this; + } + + [[nodiscard]] const std::optional& get_version() const + { + return m_version; + } + + [[nodiscard]] const hashset_t& get_allowed_flag_prefixes() const + { + return allowed_flag_prefixes; + } + + private: + void handle_compound_flags(hashset_t& found_flags, argument_storage_t& parsed_args, argument_consumer_t& consumer, + const argument_string_t& arg); + void parse_flag(argument_storage_t& parsed_args, argument_consumer_t& consumer, std::string_view arg); + void parse_positional(argument_storage_t& parsed_args, argument_consumer_t& consumer, argument_positional_storage_t& storage, + std::string_view arg); + static void handle_missing_and_default_args(hashmap_t& arguments, + const hashset_t& found, argument_storage_t& parsed_args, std::string_view type); + static expected, std::string> consume_until_flag_or_end(argument_consumer_t& consumer, + hashset_t* allowed_choices); + static std::vector consume_argc(i32 argc, argument_consumer_t& consumer, hashset_t* allowed_choices, + std::string_view arg); + + std::optional m_name; + std::optional m_usage; + std::optional m_description; + std::optional m_epilogue; + std::optional m_version; + const argument_subparser_t* m_parent = nullptr; + std::vector> m_subparsers; + std::vector> m_argument_builders; + hashmap_t m_flag_arguments; + std::vector> m_positional_arguments; + hashset_t allowed_flag_prefixes = {'-', '+', '/'}; + }; + + class argument_subparser_t + { + friend argument_parser_t; + public: + explicit argument_subparser_t(const argument_parser_t& parent): m_parent(&parent) + { + } + + template + argument_parser_t* add_parser(const std::string_view name, Aliases... aliases) + { + static_assert( + std::conjunction_v, std::is_constructible< + std::string_view, Aliases>>...>, + "Arguments must be of type string_view, convertible to string_view or be string_view constructable"); + m_parsers.emplace_back(new argument_parser_t{this}); + m_aliases[name] = m_parsers.back().get(); + ((m_aliases[std::string_view{aliases}] = m_parsers.back().get()), ...); + return m_parsers.back().get(); + } + + argument_subparser_t* set_help(const std::optional& help) + { + m_help = help; + return this; + } + + + /** + * Parses the next argument using the provided argument consumer. + * + * This function uses an argument consumer to extract and process the next argument. + * If the argument is a flag or if it cannot be matched against the available parsers, + * an exception is thrown. + * + * @param consumer Reference to an argument_consumer_t object, which handles argument parsing. + * The consumer provides the next argument to be parsed. + * + * @throws detail::subparse_error If the argument is a flag or does not match any known parser. + */ + std::pair parse(argument_consumer_t& consumer); // NOLINT + + private: + [[nodiscard]] hashmap_t> get_allowed_strings() const; + + // annoying compatability because im lazy + static std::vector> to_vec(const hashmap_t>& map); + + const argument_parser_t* m_parent; + std::optional m_last_parsed_parser; // bad hack + std::optional m_help; + std::vector> m_parsers; + hashmap_t m_aliases; + }; +} + +#endif //BLT_PARSE_ARGPARSE_V2_H diff --git a/include/blt/parse/templating.h b/include/blt/parse/templating.h index cc9efa5..7cd9d30 100644 --- a/include/blt/parse/templating.h +++ b/include/blt/parse/templating.h @@ -27,7 +27,7 @@ #include #include #include -#include +#include #include namespace blt @@ -223,8 +223,12 @@ namespace blt std::string_view from_last() { - if (!hasNext()) - return std::string_view(&raw_string[last_read_index], raw_string.size() - last_read_index); + if (!hasNext()) { + auto size = raw_string.size() - last_read_index; + if (size > 0) + return std::string_view(&raw_string[last_read_index], size); + return ""; + } auto token = storage[getCurrentIndex()]; auto len = ((&token.token.back()) - &raw_string[last_read_index]); auto str = std::string_view(&raw_string[last_read_index], len); diff --git a/include/blt/profiling/profiler.h b/include/blt/profiling/profiler.h index 2320080..74df441 100644 --- a/include/blt/profiling/profiler.h +++ b/include/blt/profiling/profiler.h @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include #include @@ -54,7 +54,7 @@ namespace blt::profiling { profile getProfile(const std::string& profileName); void printProfile( - const std::string& profileName, logging::log_level loggingLevel = logging::log_level::NONE, + const std::string& profileName, logging::log_level_t loggingLevel = logging::log_level_t::NONE, bool averageHistory = false ); diff --git a/include/blt/profiling/profiler_v2.h b/include/blt/profiling/profiler_v2.h index dd080ec..42290af 100644 --- a/include/blt/profiling/profiler_v2.h +++ b/include/blt/profiling/profiler_v2.h @@ -11,7 +11,7 @@ #include #include #include -#include +#include namespace blt { @@ -98,7 +98,7 @@ namespace blt void endInterval(interval_t* interval); void printProfile(profile_t& profiler, std::uint32_t flags = AVERAGE_HISTORY | PRINT_CYCLES | PRINT_THREAD | PRINT_WALL, - sort_by sort = sort_by::CYCLES, blt::logging::log_level log_level = blt::logging::log_level::NONE); + sort_by sort = sort_by::CYCLES, blt::logging::log_level_t log_level = blt::logging::log_level_t::NONE); void writeProfile(std::ostream& stream, profile_t& profiler, std::uint32_t flags = AVERAGE_HISTORY | PRINT_CYCLES | PRINT_THREAD | PRINT_WALL, @@ -113,7 +113,7 @@ namespace blt void endInterval(const std::string& profile_name, const std::string& interval_name); void printProfile(const std::string& profile_name, std::uint32_t flags = AVERAGE_HISTORY | PRINT_CYCLES | PRINT_THREAD | PRINT_WALL, - sort_by sort = sort_by::CYCLES, blt::logging::log_level log_level = blt::logging::log_level::NONE); + sort_by sort = sort_by::CYCLES, blt::logging::log_level_t log_level = blt::logging::log_level_t::NONE); void writeProfile(std::ostream& stream, const std::string& profile_name, std::uint32_t flags = AVERAGE_HISTORY | PRINT_CYCLES | PRINT_THREAD | PRINT_WALL, diff --git a/include/blt/std/allocator.h b/include/blt/std/allocator.h index 03e466e..e812428 100644 --- a/include/blt/std/allocator.h +++ b/include/blt/std/allocator.h @@ -17,944 +17,949 @@ */ #ifndef BLT_ALLOCATOR_H - - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include "logging.h" - #include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "blt/logging/logging.h" +#include #ifdef __unix__ - - #include + +#include #endif namespace blt { - - template - class allocator_base - { - public: - template - inline void construct(U* p, Args&& ... args) - { - ::new((void*) p) U(std::forward(args)...); - } - - template - inline void destroy(U* p) - { - if (p != nullptr) - p->~U(); - } - - [[nodiscard]] inline size_t max_size() const - { - return std::numeric_limits::max(); - } - - inline const_pointer address(const value_type& val) - { - return std::addressof(val); - } - - inline pointer address(value_type& val) - { - return std::addressof(val); - } - }; - - template - class area_allocator : public allocator_base - { - public: - using value = T; - using type = T; - using value_type = type; - using pointer = type*; - using const_pointer = const type*; - using void_pointer = void*; - using const_void_pointer = const void*; - using reference = value_type&; - using const_reference = const value_type&; - using size_type = size_t; - using difference_type = size_t; - using propagate_on_container_move_assignment = std::false_type; - template - struct rebind - { - typedef blt::area_allocator other; - }; - using allocator_base::allocator_base; - private: - /** - * Stores a view to a region of memory that has been deallocated - * This is a non-owning reference to the memory block - * - * pointer p is the pointer to the beginning of the block of memory - * size_t n is the number of elements that this block can hold - */ - struct pointer_view - { - pointer p; - size_t n; - }; - - /** - * Stores the actual data for allocated blocks. Since we would like to be able to allocate an arbitrary number of items - * we need a way of storing that data. The block storage holds an owning pointer to a region of memory with used elements - * Only up to used has to have their destructors called, which should be handled by the deallocate function - * it is UB to not deallocate memory allocated by this allocator - * - * an internal vector is used to store the regions of memory which have been deallocated. the allocate function will search for - * free blocks with sufficient size in order to maximize memory usage. In the future more advanced methods should be used - * for both faster access to deallocated blocks of sufficient size and to ensure coherent memory. - */ - struct block_storage - { - pointer data; - size_t used = 0; - // TODO: b-tree? - std::vector unallocated_blocks; - }; - - /** - * Stores an index to a pointer_view along with the amount of memory leftover after the allocation - * it also stores the block being allocated to in question. The new inserted leftover should start at old_ptr + size - */ - struct block_view - { - block_storage* blk; - size_t index; - size_t leftover; - - block_view(block_storage* blk, size_t index, size_t leftover): blk(blk), index(index), leftover(leftover) - {} - }; - - /** - * Allocate a new block of memory and push it to the back of blocks. - */ - inline void allocate_block() - { - //BLT_INFO("Allocating a new block of size %d", BLOCK_SIZE); - auto* blk = new block_storage(); - blk->data = static_cast(malloc(sizeof(T) * BLOCK_SIZE)); - blocks.push_back(blk); - } - - /** - * Searches for a free block inside the block storage with sufficient space and returns an optional view to it - * The optional will be empty if no open block can be found. - */ - inline std::optional search_for_block(block_storage* blk, size_t n) - { - for (auto [index, item] : blt::enumerate(blk->unallocated_blocks)) - { - if (item.n >= n) - return block_view{blk, index, item.n - n}; - } - return {}; - } - - /** - * removes the block of memory from the unallocated_blocks storage in the underlying block, inserting a new unallocated block if - * there was any leftover. Returns a pointer to the beginning of the new block. - */ - inline pointer swap_pop_resize_if(const block_view& view, size_t n) - { - pointer_view ptr = view.blk->unallocated_blocks[view.index]; - std::iter_swap(view.blk->unallocated_blocks.begin() + view.index, view.blk->unallocated_blocks.end() - 1); - view.blk->unallocated_blocks.pop_back(); - if (view.leftover > 0) - view.blk->unallocated_blocks.push_back({ptr.p + n, view.leftover}); - return ptr.p; - } - - /** - * Finds the next available unallocated block of memory, or empty if there is none which meet size requirements - */ - inline std::optional find_available_block(size_t n) - { - for (auto* blk : blocks) - { - if (auto view = search_for_block(blk, n)) - return swap_pop_resize_if(view.value(), n); - } - return {}; - } - - /** - * returns a pointer to a block of memory along with an offset into that pointer that the requested block can be found at - */ - inline std::pair getBlock(size_t n) - { - if (auto blk = find_available_block(n)) - return {blk.value(), 0}; - - if (blocks.back()->used + n > BLOCK_SIZE) - allocate_block(); - - auto ptr = std::pair{blocks.back()->data, blocks.back()->used}; - blocks.back()->used += n; - return ptr; - } - - /** - * Calls the constructor on elements if they require construction, otherwise constructor will not be called and this function is useless - * - * ALLOCATORS RETURN UNINIT STORAGE!! THIS HAS BEEN DISABLED. - */ - inline void allocate_in_block(pointer, size_t) - { -// if constexpr (std::is_default_constructible_v && !std::is_trivially_default_constructible_v) -// { -// for (size_t i = 0; i < n; i++) -// new(&begin[i]) T(); -// } - } - - public: - area_allocator() - { - allocate_block(); - } - - area_allocator(const area_allocator& copy) = delete; - - area_allocator(area_allocator&& move) noexcept - { - blocks = move.blocks; - } - - area_allocator& operator=(const area_allocator& copy) = delete; - - area_allocator& operator=(area_allocator&& move) noexcept - { - std::swap(move.blocks, blocks); - } - - [[nodiscard]] pointer allocate(size_t n) - { - if (n > BLOCK_SIZE) - throw std::runtime_error("Requested allocation is too large!"); - - auto block_info = getBlock(n); - - auto* ptr = &block_info.first[block_info.second]; - // call constructors on the objects if they require it - allocate_in_block(ptr, n); - - return ptr; - } - - void deallocate(pointer p, size_t n) noexcept - { - if (p == nullptr) - return; -// for (size_t i = 0; i < n; i++) -// p[i].~T(); - for (auto*& blk : blocks) - { - if (p >= blk->data && p <= (blk->data + BLOCK_SIZE)) - { - blk->unallocated_blocks.push_back(pointer_view{p, n}); - break; - } - } - } - - ~area_allocator() - { - for (auto*& blk : blocks) - { - free(blk->data); - delete blk; - } - } - - private: - std::vector blocks; - }; + template + class allocator_base + { + public: + template + inline void construct(U* p, Args&&... args) + { + ::new((void*) p) U(std::forward(args)...); + } -// template -// class bump_allocator : public allocator_base -// { -// public: -// using value = T; -// using type = T; -// using value_type = type; -// using pointer = type*; -// using const_pointer = const type*; -// using void_pointer = void*; -// using const_void_pointer = const void*; -// using reference = value_type&; -// using const_reference = const value_type&; -// using size_type = size_t; -// using difference_type = size_t; -// using propagate_on_container_move_assignment = std::false_type; -// template -// struct rebind -// { -// typedef blt::bump_allocator other; -// }; -// using allocator_base::allocator_base; -// private: -// pointer buffer_; -// blt::size_t offset_; -// blt::size_t size_; -// public: -// explicit bump_allocator(blt::size_t size): buffer_(static_cast(malloc(size * sizeof(T)))), offset_(0), size_(size) -// {} -// -// template -// explicit bump_allocator(blt::size_t size, Args&& ... defaults): -// buffer_(static_cast(malloc(size * sizeof(type)))), offset_(0), size_(size) -// { -// for (blt::size_t i = 0; i < size_; i++) -// ::new(&buffer_[i]) T(std::forward(defaults)...); -// } -// -// bump_allocator(pointer buffer, blt::size_t size): buffer_(buffer), offset_(0), size_(size) -// {} -// -// bump_allocator(const bump_allocator& copy) = delete; -// -// bump_allocator(bump_allocator&& move) noexcept -// { -// buffer_ = move.buffer_; -// size_ = move.size_; -// offset_ = move.offset_; -// } -// -// bump_allocator& operator=(const bump_allocator& copy) = delete; -// -// bump_allocator& operator=(bump_allocator&& move) noexcept -// { -// std::swap(move.buffer_, buffer_); -// std::swap(move.size_, size_); -// std::swap(move.offset_, offset_); -// } -// -// pointer allocate(blt::size_t n) -// { -// auto nv = offset_ + n; -// if (nv > size_) -// throw std::bad_alloc(); -// pointer b = &buffer_[offset_]; -// offset_ = nv; -// return b; -// } -// -// void deallocate(pointer, blt::size_t) -// {} -// -// ~bump_allocator() -// { -// free(buffer_); -// } -// }; - - /** - * The bump allocator is meant to be a faster area allocator which will only allocate forward through either a supplied buffer or size - * or will create a linked list type data structure of buffered blocks. - * @tparam ALLOC allocator to use for any allocations. In the case of the non-linked variant, this will be used if a size is supplied. The supplied buffer must be allocated with this allocator! - * @tparam linked use a linked list to allocate with the allocator or just use the supplied buffer and throw an exception of we cannot allocate - */ - template typename ALLOC = std::allocator> - class bump_allocator_old; - - template typename ALLOC> - class bump_allocator_old - { - private: - ALLOC allocator; - blt::u8* buffer_; - blt::u8* offset_; - blt::size_t size_; - public: - explicit bump_allocator_old(blt::size_t size): buffer_(static_cast(allocator.allocate(size))), offset_(buffer_), size_(size) - {} - - explicit bump_allocator_old(blt::u8* buffer, blt::size_t size): buffer_(buffer), offset_(buffer), size_(size) - {} - - template - [[nodiscard]] T* allocate() - { - size_t remaining_num_bytes = size_ - static_cast(buffer_ - offset_); - auto pointer = static_cast(offset_); - const auto aligned_address = std::align(alignof(T), sizeof(T), pointer, remaining_num_bytes); - if (aligned_address == nullptr) - throw std::bad_alloc{}; - offset_ = static_cast(aligned_address) + sizeof(T); - return static_cast(aligned_address); - } - - template - [[nodiscard]] T* emplace(Args&& ... args) - { - const auto allocated_memory = allocate(); - return new(allocated_memory) T{std::forward(args)...}; - } - - template - inline void construct(U* p, Args&& ... args) - { - ::new((void*) p) U(std::forward(args)...); - } - - template - inline void destroy(U* p) - { - if (p != nullptr) - p->~U(); - } - - ~bump_allocator_old() - { - allocator.deallocate(buffer_, size_); - } - }; - - template typename ALLOC> - class bump_allocator_old - { - private: - struct block - { - blt::size_t allocated_objects = 0; - blt::u8* buffer = nullptr; - blt::u8* offset = nullptr; - - explicit block(blt::u8* buffer): buffer(buffer), offset(buffer) - {} - }; - - ALLOC allocator; - std::vector> blocks; - blt::size_t size_; - blt::size_t allocations = 0; - blt::size_t deallocations = 0; - - void expand() - { - auto ptr = static_cast(allocator.allocate(size_)); - blocks.push_back(block{ptr}); - allocations++; - } - - template - T* allocate_back() - { - auto& back = blocks.back(); - size_t remaining_bytes = size_ - static_cast(back.offset - back.buffer); - auto pointer = static_cast(back.offset); - const auto aligned_address = std::align(alignof(T), sizeof(T), pointer, remaining_bytes); - if (aligned_address != nullptr) - { - back.offset = static_cast(aligned_address) + sizeof(T); - back.allocated_objects++; - } - - return static_cast(aligned_address); - } - - public: - /** - * @param size of the list blocks - */ - explicit bump_allocator_old(blt::size_t size): size_(size) - { - expand(); - } - - template - [[nodiscard]] T* allocate() - { - if (auto ptr = allocate_back(); ptr == nullptr) - expand(); - else - return ptr; - if (auto ptr = allocate_back(); ptr == nullptr) - throw std::bad_alloc(); - else - return ptr; - } - - template - void deallocate(T* p) - { - auto* ptr = reinterpret_cast(p); - for (auto e : blt::enumerate(blocks)) - { - auto& block = e.second; - if (ptr >= block.buffer && ptr <= block.offset) - { - block.allocated_objects--; - if (block.allocated_objects == 0) - { - std::iter_swap(blocks.begin() + e.first, blocks.end() - 1); - allocator.deallocate(blocks.back().buffer, size_); - blocks.pop_back(); - deallocations++; - } - return; - } - } - } - - template - [[nodiscard]] T* emplace(Args&& ... args) - { - const auto allocated_memory = allocate(); - return new(allocated_memory) T{std::forward(args)...}; - } - - template - inline void construct(U* p, Args&& ... args) - { - ::new((void*) p) U(std::forward(args)...); - } - - template - inline void destroy(U* p) - { - if (p != nullptr) - p->~U(); - } - - ~bump_allocator_old() - { - if (allocations != deallocations) - BLT_WARN("Allocator has blocks which have not been deallocated! Destructors might not have been called!"); - for (auto& v : blocks) - allocator.deallocate(v.buffer, size_); - } - }; - - template - static inline T* allocate_huge_page(blt::size_t BLOCK_SIZE, blt::size_t HUGE_PAGE_SIZE = BLT_2MB_SIZE) - { -#ifdef __unix__ - BLT_ASSERT((BLOCK_SIZE & (HUGE_PAGE_SIZE - 1)) == 0 && "Must be multiple of the huge page size!"); - T* buffer = static_cast(mmap(nullptr, BLOCK_SIZE, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB | MAP_POPULATE, -1, 0)); - // if we fail to allocate a huge page we can try to allocate normally - if (buffer == MAP_FAILED) - { - if constexpr (WARN_ON_FAIL) - { - BLT_WARN_STREAM << "We failed to allocate huge pages\n"; - BLT_WARN_STREAM << handle_mmap_error(); - BLT_WARN_STREAM << "\033[1;31mYou should attempt to enable " - "huge pages as this will allocate normal pages and double the memory usage!\033[22m\n"; - } - blt::size_t bytes = BLOCK_SIZE * 2; - buffer = static_cast(mmap(nullptr, bytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE, -1, 0)); - if (buffer == MAP_FAILED) - { - BLT_ERROR_STREAM << "Failed to allocate normal pages\n"; - throw bad_alloc_t(handle_mmap_error()); - } - if constexpr (WARN_ON_FAIL) - { - if (((size_t) buffer & (HUGE_PAGE_SIZE - 1)) != 0) - BLT_ERROR("Pointer is not aligned! %p", buffer); - } - auto* ptr = static_cast(buffer); - auto ptr_size = reinterpret_cast(ptr); - buffer = static_cast(std::align(BLOCK_SIZE, BLOCK_SIZE, ptr, bytes)); - if constexpr (WARN_ON_FAIL) - BLT_ERROR("Offset by %ld pages, resulting: %p", (reinterpret_cast(buffer) - ptr_size) / 4096, buffer); - } - return buffer; -#endif - return malloc(BLOCK_SIZE); - } - - /** - * blt::bump_allocator. Allocates blocks of BLOCK_SIZE with zero reuse. When all objects from a block are fully deallocated the block will be freed - * @tparam BLOCK_SIZE size of block to use. recommended to be multiple of page size or huge page size. - * @tparam USE_HUGE allocate using mmap and huge pages. If this fails it will use mmap to allocate normally. defaults to off because linux has parent huge pages. - * @tparam HUGE_PAGE_SIZE size the system allows huge pages to be. defaults to 2mb - * @tparam WARN_ON_FAIL print warning messages if allocating huge pages fail - */ - template - class bump_allocator - { - // ensure power of two - static_assert(((BLOCK_SIZE & (BLOCK_SIZE - 1)) == 0) && "Must be a power of two!"); - public: - /** - * convert any pointer back into a pointer its block - */ - template - static inline auto to_block(T* p) - { - return reinterpret_cast(reinterpret_cast(p) & static_cast(~(BLOCK_SIZE - 1))); - } - - class stats_t - { - friend bump_allocator; - private: - blt::size_t allocated_blocks = 0; - blt::size_t allocated_bytes = 0; - blt::size_t peak_blocks = 0; - blt::size_t peak_bytes = 0; - protected: - inline void incrementBlocks() - { - allocated_blocks++; - if (allocated_blocks > peak_blocks) - peak_blocks = allocated_blocks; - } - - inline void decrementBlocks() - { - allocated_blocks--; - } - - inline void incrementBytes(blt::size_t bytes) - { - allocated_bytes += bytes; - if (allocated_bytes > peak_bytes) - peak_bytes = allocated_bytes; - } - - inline void decrementBytes(blt::size_t bytes) - { - allocated_bytes -= bytes; - } - - public: - - inline auto getAllocatedBlocks() const - { - return allocated_blocks; - } - - inline auto getAllocatedBytes() const - { - return allocated_bytes; - } - - inline auto getPeakBlocks() const - { - return peak_blocks; - } - - inline auto getPeakBytes() const - { - return peak_bytes; - } - }; - - private: - stats_t stats; - //blt::hashset_t deletes; - - struct block - { - struct block_metadata_t - { - blt::size_t allocated_objects = 0; - block* next = nullptr; - block* prev = nullptr; - blt::u8* offset = nullptr; - } metadata; - blt::u8 buffer[BLOCK_SIZE - sizeof(block_metadata_t)]{}; - - block() - { - metadata.offset = buffer; - } - }; - - // remaining space inside the block after accounting for the metadata - static constexpr blt::size_t BLOCK_REMAINDER = BLOCK_SIZE - sizeof(typename block::block_metadata_t); - - block* base = nullptr; - block* head = nullptr; - - /** - * Handles the allocation of the bytes for the block. - * This function will either use mmap to allocate huge pages if requested - * or use std::align_alloc to create an aligned allocation - * @return pointer to a constructed block - */ - block* allocate_block() - { - block* buffer; -#ifdef __unix__ - if constexpr (USE_HUGE) - { - buffer = allocate_huge_page(BLOCK_SIZE, HUGE_PAGE_SIZE); - } else - buffer = reinterpret_cast(std::aligned_alloc(BLOCK_SIZE, BLOCK_SIZE)); -#else + template + inline void destroy(U* p) + { + if (p != nullptr) + p->~U(); + } + + [[nodiscard]] inline size_t max_size() const + { + return std::numeric_limits::max(); + } + + inline const_pointer address(const value_type& val) + { + return std::addressof(val); + } + + inline pointer address(value_type& val) + { + return std::addressof(val); + } + }; + + template + class area_allocator : public allocator_base + { + public: + using value = T; + using type = T; + using value_type = type; + using pointer = type*; + using const_pointer = const type*; + using void_pointer = void*; + using const_void_pointer = const void*; + using reference = value_type&; + using const_reference = const value_type&; + using size_type = size_t; + using difference_type = size_t; + using propagate_on_container_move_assignment = std::false_type; + + template + struct rebind + { + typedef blt::area_allocator other; + }; + + using allocator_base::allocator_base; + + private: + /** + * Stores a view to a region of memory that has been deallocated + * This is a non-owning reference to the memory block + * + * pointer p is the pointer to the beginning of the block of memory + * size_t n is the number of elements that this block can hold + */ + struct pointer_view + { + pointer p; + size_t n; + }; + + /** + * Stores the actual data for allocated blocks. Since we would like to be able to allocate an arbitrary number of items + * we need a way of storing that data. The block storage holds an owning pointer to a region of memory with used elements + * Only up to used has to have their destructors called, which should be handled by the deallocate function + * it is UB to not deallocate memory allocated by this allocator + * + * an internal vector is used to store the regions of memory which have been deallocated. the allocate function will search for + * free blocks with sufficient size in order to maximize memory usage. In the future more advanced methods should be used + * for both faster access to deallocated blocks of sufficient size and to ensure coherent memory. + */ + struct block_storage + { + pointer data; + size_t used = 0; + // TODO: b-tree? + std::vector unallocated_blocks; + }; + + /** + * Stores an index to a pointer_view along with the amount of memory leftover after the allocation + * it also stores the block being allocated to in question. The new inserted leftover should start at old_ptr + size + */ + struct block_view + { + block_storage* blk; + size_t index; + size_t leftover; + + block_view(block_storage* blk, size_t index, size_t leftover): blk(blk), index(index), leftover(leftover) + {} + }; + + /** + * Allocate a new block of memory and push it to the back of blocks. + */ + inline void allocate_block() + { + //BLT_INFO("Allocating a new block of size {:d}", BLOCK_SIZE); + auto* blk = new block_storage(); + blk->data = static_cast(malloc(sizeof(T) * BLOCK_SIZE)); + blocks.push_back(blk); + } + + /** + * Searches for a free block inside the block storage with sufficient space and returns an optional view to it + * The optional will be empty if no open block can be found. + */ + inline std::optional search_for_block(block_storage* blk, size_t n) + { + for (auto [index, item] : blt::enumerate(blk->unallocated_blocks)) + { + if (item.n >= n) + return block_view{blk, index, item.n - n}; + } + return {}; + } + + /** + * removes the block of memory from the unallocated_blocks storage in the underlying block, inserting a new unallocated block if + * there was any leftover. Returns a pointer to the beginning of the new block. + */ + inline pointer swap_pop_resize_if(const block_view& view, size_t n) + { + pointer_view ptr = view.blk->unallocated_blocks[view.index]; + std::iter_swap(view.blk->unallocated_blocks.begin() + view.index, view.blk->unallocated_blocks.end() - 1); + view.blk->unallocated_blocks.pop_back(); + if (view.leftover > 0) + view.blk->unallocated_blocks.push_back({ptr.p + n, view.leftover}); + return ptr.p; + } + + /** + * Finds the next available unallocated block of memory, or empty if there is none which meet size requirements + */ + inline std::optional find_available_block(size_t n) + { + for (auto* blk : blocks) + { + if (auto view = search_for_block(blk, n)) + return swap_pop_resize_if(view.value(), n); + } + return {}; + } + + /** + * returns a pointer to a block of memory along with an offset into that pointer that the requested block can be found at + */ + inline std::pair getBlock(size_t n) + { + if (auto blk = find_available_block(n)) + return {blk.value(), 0}; + + if (blocks.back()->used + n > BLOCK_SIZE) + allocate_block(); + + auto ptr = std::pair{blocks.back()->data, blocks.back()->used}; + blocks.back()->used += n; + return ptr; + } + + /** + * Calls the constructor on elements if they require construction, otherwise constructor will not be called and this function is useless + * + * ALLOCATORS RETURN UNINIT STORAGE!! THIS HAS BEEN DISABLED. + */ + inline void allocate_in_block(pointer, size_t) + { + // if constexpr (std::is_default_constructible_v && !std::is_trivially_default_constructible_v) + // { + // for (size_t i = 0; i < n; i++) + // new(&begin[i]) T(); + // } + } + + public: + area_allocator() + { + allocate_block(); + } + + area_allocator(const area_allocator& copy) = delete; + + area_allocator(area_allocator&& move) noexcept + { + blocks = move.blocks; + } + + area_allocator& operator=(const area_allocator& copy) = delete; + + area_allocator& operator=(area_allocator&& move) noexcept + { + std::swap(move.blocks, blocks); + } + + [[nodiscard]] pointer allocate(size_t n) + { + if (n > BLOCK_SIZE) + throw std::runtime_error("Requested allocation is too large!"); + + auto block_info = getBlock(n); + + auto* ptr = &block_info.first[block_info.second]; + // call constructors on the objects if they require it + allocate_in_block(ptr, n); + + return ptr; + } + + void deallocate(pointer p, size_t n) noexcept + { + if (p == nullptr) + return; + // for (size_t i = 0; i < n; i++) + // p[i].~T(); + for (auto*& blk : blocks) + { + if (p >= blk->data && p <= (blk->data + BLOCK_SIZE)) + { + blk->unallocated_blocks.push_back(pointer_view{p, n}); + break; + } + } + } + + ~area_allocator() + { + for (auto*& blk : blocks) + { + free(blk->data); + delete blk; + } + } + + private: + std::vector blocks; + }; + + // template + // class bump_allocator : public allocator_base + // { + // public: + // using value = T; + // using type = T; + // using value_type = type; + // using pointer = type*; + // using const_pointer = const type*; + // using void_pointer = void*; + // using const_void_pointer = const void*; + // using reference = value_type&; + // using const_reference = const value_type&; + // using size_type = size_t; + // using difference_type = size_t; + // using propagate_on_container_move_assignment = std::false_type; + // template + // struct rebind + // { + // typedef blt::bump_allocator other; + // }; + // using allocator_base::allocator_base; + // private: + // pointer buffer_; + // blt::size_t offset_; + // blt::size_t size_; + // public: + // explicit bump_allocator(blt::size_t size): buffer_(static_cast(malloc(size * sizeof(T)))), offset_(0), size_(size) + // {} + // + // template + // explicit bump_allocator(blt::size_t size, Args&& ... defaults): + // buffer_(static_cast(malloc(size * sizeof(type)))), offset_(0), size_(size) + // { + // for (blt::size_t i = 0; i < size_; i++) + // ::new(&buffer_[i]) T(std::forward(defaults)...); + // } + // + // bump_allocator(pointer buffer, blt::size_t size): buffer_(buffer), offset_(0), size_(size) + // {} + // + // bump_allocator(const bump_allocator& copy) = delete; + // + // bump_allocator(bump_allocator&& move) noexcept + // { + // buffer_ = move.buffer_; + // size_ = move.size_; + // offset_ = move.offset_; + // } + // + // bump_allocator& operator=(const bump_allocator& copy) = delete; + // + // bump_allocator& operator=(bump_allocator&& move) noexcept + // { + // std::swap(move.buffer_, buffer_); + // std::swap(move.size_, size_); + // std::swap(move.offset_, offset_); + // } + // + // pointer allocate(blt::size_t n) + // { + // auto nv = offset_ + n; + // if (nv > size_) + // throw std::bad_alloc(); + // pointer b = &buffer_[offset_]; + // offset_ = nv; + // return b; + // } + // + // void deallocate(pointer, blt::size_t) + // {} + // + // ~bump_allocator() + // { + // free(buffer_); + // } + // }; + + /** + * The bump allocator is meant to be a faster area allocator which will only allocate forward through either a supplied buffer or size + * or will create a linked list type data structure of buffered blocks. + * @tparam ALLOC allocator to use for any allocations. In the case of the non-linked variant, this will be used if a size is supplied. The supplied buffer must be allocated with this allocator! + * @tparam linked use a linked list to allocate with the allocator or just use the supplied buffer and throw an exception of we cannot allocate + */ + template typename ALLOC = std::allocator> + class bump_allocator_old; + + template typename ALLOC> + class bump_allocator_old + { + private: + ALLOC allocator; + blt::u8* buffer_; + blt::u8* offset_; + blt::size_t size_; + + public: + explicit bump_allocator_old(blt::size_t size): buffer_(static_cast(allocator.allocate(size))), offset_(buffer_), size_(size) + {} + + explicit bump_allocator_old(blt::u8* buffer, blt::size_t size): buffer_(buffer), offset_(buffer), size_(size) + {} + + template + [[nodiscard]] T* allocate() + { + size_t remaining_num_bytes = size_ - static_cast(buffer_ - offset_); + auto pointer = static_cast(offset_); + const auto aligned_address = std::align(alignof(T), sizeof(T), pointer, remaining_num_bytes); + if (aligned_address == nullptr) + throw std::bad_alloc{}; + offset_ = static_cast(aligned_address) + sizeof(T); + return static_cast(aligned_address); + } + + template + [[nodiscard]] T* emplace(Args&&... args) + { + const auto allocated_memory = allocate(); + return new(allocated_memory) T{std::forward(args)...}; + } + + template + inline void construct(U* p, Args&&... args) + { + ::new((void*) p) U(std::forward(args)...); + } + + template + inline void destroy(U* p) + { + if (p != nullptr) + p->~U(); + } + + ~bump_allocator_old() + { + allocator.deallocate(buffer_, size_); + } + }; + + template typename ALLOC> + class bump_allocator_old + { + private: + struct block + { + blt::size_t allocated_objects = 0; + blt::u8* buffer = nullptr; + blt::u8* offset = nullptr; + + explicit block(blt::u8* buffer): buffer(buffer), offset(buffer) + {} + }; + + ALLOC allocator; + std::vector> blocks; + blt::size_t size_; + blt::size_t allocations = 0; + blt::size_t deallocations = 0; + + void expand() + { + auto ptr = static_cast(allocator.allocate(size_)); + blocks.push_back(block{ptr}); + allocations++; + } + + template + T* allocate_back() + { + auto& back = blocks.back(); + size_t remaining_bytes = size_ - static_cast(back.offset - back.buffer); + auto pointer = static_cast(back.offset); + const auto aligned_address = std::align(alignof(T), sizeof(T), pointer, remaining_bytes); + if (aligned_address != nullptr) + { + back.offset = static_cast(aligned_address) + sizeof(T); + back.allocated_objects++; + } + + return static_cast(aligned_address); + } + + public: + /** + * @param size of the list blocks + */ + explicit bump_allocator_old(blt::size_t size): size_(size) + { + expand(); + } + + template + [[nodiscard]] T* allocate() + { + if (auto ptr = allocate_back(); ptr == nullptr) + expand(); + else + return ptr; + if (auto ptr = allocate_back(); ptr == nullptr) + throw std::bad_alloc(); + else + return ptr; + } + + template + void deallocate(T* p) + { + auto* ptr = reinterpret_cast(p); + for (auto e : blt::enumerate(blocks)) + { + auto& block = e.second; + if (ptr >= block.buffer && ptr <= block.offset) + { + block.allocated_objects--; + if (block.allocated_objects == 0) + { + std::iter_swap(blocks.begin() + e.first, blocks.end() - 1); + allocator.deallocate(blocks.back().buffer, size_); + blocks.pop_back(); + deallocations++; + } + return; + } + } + } + + template + [[nodiscard]] T* emplace(Args&&... args) + { + const auto allocated_memory = allocate(); + return new(allocated_memory) T{std::forward(args)...}; + } + + template + inline void construct(U* p, Args&&... args) + { + ::new((void*) p) U(std::forward(args)...); + } + + template + inline void destroy(U* p) + { + if (p != nullptr) + p->~U(); + } + + ~bump_allocator_old() + { + if (allocations != deallocations) + BLT_WARN("Allocator has blocks which have not been deallocated! Destructors might not have been called!"); + for (auto& v : blocks) + allocator.deallocate(v.buffer, size_); + } + }; + + template + static inline T* allocate_huge_page(blt::size_t BLOCK_SIZE, blt::size_t HUGE_PAGE_SIZE = BLT_2MB_SIZE) + { + #ifdef __unix__ + BLT_ASSERT((BLOCK_SIZE & (HUGE_PAGE_SIZE - 1)) == 0 && "Must be multiple of the huge page size!"); + T* buffer = static_cast(mmap(nullptr, BLOCK_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB | MAP_POPULATE, -1, + 0)); + // if we fail to allocate a huge page we can try to allocate normally + if (buffer == MAP_FAILED) + { + if constexpr (WARN_ON_FAIL) + { + BLT_WARN("We failed to allocate huge pages\n{}{}", handle_mmap_error(), + "\033[1;31mYou should attempt to enable " + "huge pages as this will allocate normal pages and double the memory usage!\033[22m\n"); + } + blt::size_t bytes = BLOCK_SIZE * 2; + buffer = static_cast(mmap(nullptr, bytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE, -1, 0)); + if (buffer == MAP_FAILED) + { + BLT_ERROR("Failed to allocate normal pages"); + throw bad_alloc_t(handle_mmap_error()); + } + if constexpr (WARN_ON_FAIL) + { + if (((size_t) buffer & (HUGE_PAGE_SIZE - 1)) != 0) + BLT_ERROR("Pointer is not aligned! {:#x}", buffer); + } + auto* ptr = static_cast(buffer); + auto ptr_size = reinterpret_cast(ptr); + buffer = static_cast(std::align(BLOCK_SIZE, BLOCK_SIZE, ptr, bytes)); + if constexpr (WARN_ON_FAIL) + BLT_ERROR("Offset by {} pages, resulting: {:#x}", (reinterpret_cast(buffer) - ptr_size) / 4096, buffer); + } + return buffer; + #endif + return malloc(BLOCK_SIZE); + } + + /** + * blt::bump_allocator. Allocates blocks of BLOCK_SIZE with zero reuse. When all objects from a block are fully deallocated the block will be freed + * @tparam BLOCK_SIZE size of block to use. recommended to be multiple of page size or huge page size. + * @tparam USE_HUGE allocate using mmap and huge pages. If this fails it will use mmap to allocate normally. defaults to off because linux has parent huge pages. + * @tparam HUGE_PAGE_SIZE size the system allows huge pages to be. defaults to 2mb + * @tparam WARN_ON_FAIL print warning messages if allocating huge pages fail + */ + template + class bump_allocator + { + // ensure power of two + static_assert(((BLOCK_SIZE & (BLOCK_SIZE - 1)) == 0) && "Must be a power of two!"); + + public: + /** + * convert any pointer back into a pointer its block + */ + template + static inline auto to_block(T* p) + { + return reinterpret_cast(reinterpret_cast(p) & static_cast(~(BLOCK_SIZE - 1))); + } + + class stats_t + { + friend bump_allocator; + + private: + blt::size_t allocated_blocks = 0; + blt::size_t allocated_bytes = 0; + blt::size_t peak_blocks = 0; + blt::size_t peak_bytes = 0; + + protected: + inline void incrementBlocks() + { + allocated_blocks++; + if (allocated_blocks > peak_blocks) + peak_blocks = allocated_blocks; + } + + inline void decrementBlocks() + { + allocated_blocks--; + } + + inline void incrementBytes(blt::size_t bytes) + { + allocated_bytes += bytes; + if (allocated_bytes > peak_bytes) + peak_bytes = allocated_bytes; + } + + inline void decrementBytes(blt::size_t bytes) + { + allocated_bytes -= bytes; + } + + public: + inline auto getAllocatedBlocks() const + { + return allocated_blocks; + } + + inline auto getAllocatedBytes() const + { + return allocated_bytes; + } + + inline auto getPeakBlocks() const + { + return peak_blocks; + } + + inline auto getPeakBytes() const + { + return peak_bytes; + } + }; + + private: + stats_t stats; + //blt::hashset_t deletes; + + struct block + { + struct block_metadata_t + { + blt::size_t allocated_objects = 0; + block* next = nullptr; + block* prev = nullptr; + blt::u8* offset = nullptr; + } metadata; + + blt::u8 buffer[BLOCK_SIZE - sizeof(block_metadata_t)]{}; + + block() + { + metadata.offset = buffer; + } + }; + + // remaining space inside the block after accounting for the metadata + static constexpr blt::size_t BLOCK_REMAINDER = BLOCK_SIZE - sizeof(typename block::block_metadata_t); + + block* base = nullptr; + block* head = nullptr; + + /** + * Handles the allocation of the bytes for the block. + * This function will either use mmap to allocate huge pages if requested + * or use std::align_alloc to create an aligned allocation + * @return pointer to a constructed block + */ + block* allocate_block() + { + block* buffer; + #ifdef __unix__ + if constexpr (USE_HUGE) + { + buffer = allocate_huge_page(BLOCK_SIZE, HUGE_PAGE_SIZE); + } else + buffer = reinterpret_cast(std::aligned_alloc(BLOCK_SIZE, BLOCK_SIZE)); + #else buffer = static_cast(_aligned_malloc(BLOCK_SIZE, BLOCK_SIZE)); -#endif - construct(buffer); -#ifndef BLT_DISABLE_STATS - stats.incrementBlocks(); -#endif - return buffer; - } - - /** - * Allocates a new block and pushes it to the front of the linked listed - */ - void allocate_forward() - { - auto* block = allocate_block(); - if (head == nullptr) - { - base = head = block; - return; - } - block->metadata.prev = head; - head->metadata.next = block; - head = block; - } - - /** - * handles the actual allocation and alignment of memory - * @param bytes number of bytes to allocate - * @param alignment alignment required - * @return aligned pointer - */ - void* allocate_bytes(blt::size_t bytes, blt::size_t alignment) - { - if (head == nullptr) - return nullptr; - blt::size_t remaining_bytes = BLOCK_REMAINDER - static_cast(head->metadata.offset - head->buffer); - auto pointer = static_cast(head->metadata.offset); - return std::align(alignment, bytes, pointer, remaining_bytes); - } - - /** - * allocate an object starting from the next available address - * @tparam T type to allocate for - * @param count number of elements to allocate - * @return nullptr if the object could not be allocated, pointer to the object if it could, pointer to the start if count != 1 - */ - template - T* allocate_object(blt::size_t count) - { - blt::size_t bytes = sizeof(T) * count; - const auto aligned_address = allocate_bytes(bytes, alignof(T)); - if (aligned_address != nullptr) - { - head->metadata.allocated_objects++; - head->metadata.offset = static_cast(aligned_address) + bytes; - } - return static_cast(aligned_address); - } - - /** - * Frees a block - * @param p pointer to the block to free - */ - inline void delete_block(block* p) - { -#ifndef BLT_DISABLE_STATS - stats.decrementBlocks(); -#endif - if constexpr (USE_HUGE) - { - if (munmap(p, BLOCK_SIZE)) - { - BLT_ERROR_STREAM << "FAILED TO DEALLOCATE BLOCK\n"; - throw bad_alloc_t(handle_mmap_error()); - } - } else - free(p); - } - - public: - bump_allocator() = default; - - /** - * Takes an unused size parameter. Purely used for compatibility with the old bump_allocator - */ - explicit bump_allocator(blt::size_t) - {} - - /** - * Allocate bytes for a type - * @tparam T type to allocate - * @param count number of elements to allocate for - * @throws std::bad_alloc - * @return aligned pointer to the beginning of the allocated memory - */ - template - [[nodiscard]] T* allocate(blt::size_t count = 1) - { - if constexpr (sizeof(T) > BLOCK_REMAINDER) - throw std::bad_alloc(); + #endif + construct(buffer); + #ifndef BLT_DISABLE_STATS + stats.incrementBlocks(); + #endif + return buffer; + } -#ifndef BLT_DISABLE_STATS - stats.incrementBytes(sizeof(T) * count); -#endif - - T* ptr = allocate_object(count); - if (ptr != nullptr) - return ptr; - allocate_forward(); - ptr = allocate_object(count); - if (ptr == nullptr) - throw std::bad_alloc(); - return ptr; - } - - /** - * Deallocate a pointer, does not call the destructor - * @tparam T type of pointer - * @param p pointer to deallocate - */ - template - void deallocate(T* p, blt::size_t count = 1) - { - if (p == nullptr) - return; -#ifndef BLT_DISABLE_STATS - stats.decrementBytes(sizeof(T) * count); -#endif -// if (deletes.contains(p)) -// { -// BLT_FATAL("pointer %p has already been freed", p); -// throw std::bad_alloc(); -// }else -// deletes.insert(static_cast(p)); - - auto blk = to_block(p); - blk->metadata.allocated_objects--; - if (blk->metadata.allocated_objects == 0) - { - //BLT_INFO("Deallocating block from %p in (1) %p current head %p, based: %p", p, blk, head, base); - if (blk == base) - { - base = base->metadata.next; - // if they were equal (single allocated block) we also need to move the head forward - if (blk == head) - head = base; - } else if (blk == head) // else, need to make sure the head ptr gets moved back, otherwise we will use a head that has been freed - head = blk->metadata.prev; - else if (blk->metadata.prev != nullptr) // finally if it wasn't the head we need to bridge the gap in the list - blk->metadata.prev->metadata.next = blk->metadata.next; - - //BLT_INFO("Deallocating block from %p in (2) %p current head %p, based: %p", p, blk, head, base); - delete_block(blk); - } - } - - /** - * allocate a type then call its constructor with arguments - * @tparam T type to construct - * @tparam Args type of args to construct with - * @param args args to construct with - * @return aligned pointer to the constructed type - */ - template - [[nodiscard]] T* emplace(Args&& ... args) - { - const auto allocated_memory = allocate(); - return new(allocated_memory) T{std::forward(args)...}; - } - - /** - * allocate an array of count T with argument(s) args and call T's constructor - * @tparam T class to construct - * @tparam Args argument types to supply to construction - * @param count size of the array to allocate in number of elements. Note calling this with count = 0 is equivalent to calling emplace - * @param args the args to supply to construction - * @return aligned pointer to the beginning of the array of T - */ - template - [[nodiscard]] T* emplace_many(blt::size_t count, Args&& ... args) - { - if (count == 0) - return nullptr; - const auto allocated_memory = allocate(count); - for (blt::size_t i = 0; i < count; i++) - new(allocated_memory + i) T{std::forward(args)...}; - return allocated_memory; - } - - /** - * Used to construct a class U with parameters Args - * @tparam U class to construct - * @tparam Args args to use - * @param p pointer to non-constructed memory - * @param args list of arguments to build the class with - */ - template - inline void construct(U* p, Args&& ... args) - { - ::new((void*) p) U(std::forward(args)...); - } - - /** - * Call the destructor for class U with pointer p - * @tparam U class to call destructor on, this will not do anything if the type is std::trivially_destructible - * @param p - */ - template - inline void destroy(U* p) - { - if constexpr (!std::is_trivially_destructible_v) - { - if (p != nullptr) - p->~U(); - } - } - - /** - * Calls destroy on pointer p - * Then calls deallocate on p - * @tparam U class to destroy - * @param p pointer to deallocate - */ - template - inline void destruct(U* p) - { - destroy(p); - deallocate(p); - } - - inline void resetStats() - { - stats = {}; - } - - inline const auto& getStats() const - { - return stats; - } - - ~bump_allocator() - { - block* next = base; - while (next != nullptr) - { - auto* after = next->metadata.next; - delete_block(next); - next = after; - } - } - }; + /** + * Allocates a new block and pushes it to the front of the linked listed + */ + void allocate_forward() + { + auto* block = allocate_block(); + if (head == nullptr) + { + base = head = block; + return; + } + block->metadata.prev = head; + head->metadata.next = block; + head = block; + } + + /** + * handles the actual allocation and alignment of memory + * @param bytes number of bytes to allocate + * @param alignment alignment required + * @return aligned pointer + */ + void* allocate_bytes(blt::size_t bytes, blt::size_t alignment) + { + if (head == nullptr) + return nullptr; + blt::size_t remaining_bytes = BLOCK_REMAINDER - static_cast(head->metadata.offset - head->buffer); + auto pointer = static_cast(head->metadata.offset); + return std::align(alignment, bytes, pointer, remaining_bytes); + } + + /** + * allocate an object starting from the next available address + * @tparam T type to allocate for + * @param count number of elements to allocate + * @return nullptr if the object could not be allocated, pointer to the object if it could, pointer to the start if count != 1 + */ + template + T* allocate_object(blt::size_t count) + { + blt::size_t bytes = sizeof(T) * count; + const auto aligned_address = allocate_bytes(bytes, alignof(T)); + if (aligned_address != nullptr) + { + head->metadata.allocated_objects++; + head->metadata.offset = static_cast(aligned_address) + bytes; + } + return static_cast(aligned_address); + } + + /** + * Frees a block + * @param p pointer to the block to free + */ + inline void delete_block(block* p) + { + #ifndef BLT_DISABLE_STATS + stats.decrementBlocks(); + #endif + if constexpr (USE_HUGE) + { + if (munmap(p, BLOCK_SIZE)) + { + BLT_ERROR("FAILED TO DEALLOCATE BLOCK"); + throw bad_alloc_t(handle_mmap_error()); + } + } else + free(p); + } + + public: + bump_allocator() = default; + + /** + * Takes an unused size parameter. Purely used for compatibility with the old bump_allocator + */ + explicit bump_allocator(blt::size_t) + {} + + /** + * Allocate bytes for a type + * @tparam T type to allocate + * @param count number of elements to allocate for + * @throws std::bad_alloc + * @return aligned pointer to the beginning of the allocated memory + */ + template + [[nodiscard]] T* allocate(blt::size_t count = 1) + { + if constexpr (sizeof(T) > BLOCK_REMAINDER) + throw std::bad_alloc(); + + #ifndef BLT_DISABLE_STATS + stats.incrementBytes(sizeof(T) * count); + #endif + + T* ptr = allocate_object(count); + if (ptr != nullptr) + return ptr; + allocate_forward(); + ptr = allocate_object(count); + if (ptr == nullptr) + throw std::bad_alloc(); + return ptr; + } + + /** + * Deallocate a pointer, does not call the destructor + * @tparam T type of pointer + * @param p pointer to deallocate + */ + template + void deallocate(T* p, blt::size_t count = 1) + { + if (p == nullptr) + return; + #ifndef BLT_DISABLE_STATS + stats.decrementBytes(sizeof(T) * count); + #endif + // if (deletes.contains(p)) + // { + // BLT_FATAL("pointer {:#x} has already been freed", p); + // throw std::bad_alloc(); + // }else + // deletes.insert(static_cast(p)); + + auto blk = to_block(p); + blk->metadata.allocated_objects--; + if (blk->metadata.allocated_objects == 0) + { + //BLT_INFO("Deallocating block from {:#x} in (1) {:#x} current head {:#x}, based: {:#x}", p, blk, head, base); + if (blk == base) + { + base = base->metadata.next; + // if they were equal (single allocated block) we also need to move the head forward + if (blk == head) + head = base; + } else if (blk == head) // else, need to make sure the head ptr gets moved back, otherwise we will use a head that has been freed + head = blk->metadata.prev; + else if (blk->metadata.prev != nullptr) // finally if it wasn't the head we need to bridge the gap in the list + blk->metadata.prev->metadata.next = blk->metadata.next; + + //BLT_INFO("Deallocating block from {:#x} in (2) {:#x} current head {:#x}, based: {:#x}", p, blk, head, base); + delete_block(blk); + } + } + + /** + * allocate a type then call its constructor with arguments + * @tparam T type to construct + * @tparam Args type of args to construct with + * @param args args to construct with + * @return aligned pointer to the constructed type + */ + template + [[nodiscard]] T* emplace(Args&&... args) + { + const auto allocated_memory = allocate(); + return new(allocated_memory) T{std::forward(args)...}; + } + + /** + * allocate an array of count T with argument(s) args and call T's constructor + * @tparam T class to construct + * @tparam Args argument types to supply to construction + * @param count size of the array to allocate in number of elements. Note calling this with count = 0 is equivalent to calling emplace + * @param args the args to supply to construction + * @return aligned pointer to the beginning of the array of T + */ + template + [[nodiscard]] T* emplace_many(blt::size_t count, Args&&... args) + { + if (count == 0) + return nullptr; + const auto allocated_memory = allocate(count); + for (blt::size_t i = 0; i < count; i++) + new(allocated_memory + i) T{std::forward(args)...}; + return allocated_memory; + } + + /** + * Used to construct a class U with parameters Args + * @tparam U class to construct + * @tparam Args args to use + * @param p pointer to non-constructed memory + * @param args list of arguments to build the class with + */ + template + inline void construct(U* p, Args&&... args) + { + ::new((void*) p) U(std::forward(args)...); + } + + /** + * Call the destructor for class U with pointer p + * @tparam U class to call destructor on, this will not do anything if the type is std::trivially_destructible + * @param p + */ + template + inline void destroy(U* p) + { + if constexpr (!std::is_trivially_destructible_v) + { + if (p != nullptr) + p->~U(); + } + } + + /** + * Calls destroy on pointer p + * Then calls deallocate on p + * @tparam U class to destroy + * @param p pointer to deallocate + */ + template + inline void destruct(U* p) + { + destroy(p); + deallocate(p); + } + + inline void resetStats() + { + stats = {}; + } + + inline const auto& getStats() const + { + return stats; + } + + ~bump_allocator() + { + block* next = base; + while (next != nullptr) + { + auto* after = next->metadata.next; + delete_block(next); + next = after; + } + } + }; } #define BLT_ALLOCATOR_H diff --git a/include/blt/std/bump_allocator.h b/include/blt/std/bump_allocator.h index f713a14..8065671 100644 --- a/include/blt/std/bump_allocator.h +++ b/include/blt/std/bump_allocator.h @@ -29,7 +29,7 @@ #include #include #include -#include "logging.h" +#include "blt/logging/logging.h" #include #include diff --git a/include/blt/std/expected.h b/include/blt/std/expected.h index f45dcb5..d444fa2 100644 --- a/include/blt/std/expected.h +++ b/include/blt/std/expected.h @@ -375,12 +375,12 @@ namespace blt }; template - class expected + class expected : public expected { public: using expected::expected; - constexpr expected(const expected& copy) = delete; + constexpr expected(const expected& copy) = delete; expected& operator=(const expected& copy) = delete; }; diff --git a/include/blt/std/logging.h b/include/blt/std/logging.h deleted file mode 100644 index f03a7a6..0000000 --- a/include/blt/std/logging.h +++ /dev/null @@ -1,929 +0,0 @@ -/* - * Created by Brett on 20/07/23. - * Licensed under GNU General Public License V3.0 - * See LICENSE file for license detail - */ - -#ifndef BLT_TESTS_LOGGING2_H -#define BLT_TESTS_LOGGING2_H - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace blt::logging -{ - - namespace ansi - { - inline auto ESC(std::string_view str) - { - return std::string("\033") += str; - } - - inline const auto CUR_HOME = ESC("[H"); - - inline auto CUR_MOVE(blt::size_t line, blt::size_t column) - { - return ESC("[{" + std::to_string(line) + "};{" + std::to_string(column) + "}H"); - } - - inline auto CUR_UP(blt::size_t lines) - { - return ESC("[" + std::to_string(lines) + "A"); - } - - inline auto CUR_DOWN(blt::size_t lines) - { - return ESC("[" + std::to_string(lines) + "B"); - } - - inline auto CUR_RIGHT(blt::size_t columns) - { - return ESC("[" + std::to_string(columns) + "C"); - } - - inline auto CUR_LEFT(blt::size_t columns) - { - return ESC("[" + std::to_string(columns) + "D"); - } - - inline auto CUR_BEGIN_NEXT(blt::size_t lines_down) - { - return ESC("[" + std::to_string(lines_down) + "E"); - } - - inline auto CUR_BEGIN_PREV(blt::size_t lines_up) - { - return ESC("[" + std::to_string(lines_up) + "F"); - } - - inline auto CUR_COLUMN(blt::size_t column) - { - return ESC("[" + std::to_string(column) + "G"); - } - - inline auto CUR_POS() - { - return ESC("[6n"); - } - - inline auto CUR_SCROLL_UP() - { - return ESC(" M"); - } - - inline auto CUR_SAVE_DEC() - { - return ESC(" 7"); - } - - inline auto CUR_LOAD_DEC() - { - return ESC(" 8"); - } - - inline auto CUR_SAVE_SCO() - { - return ESC("[s"); - } - - inline auto CUR_LOAD_SCO() - { - return ESC("[u"); - } - - inline auto RESET = ESC("[0m"); - inline auto BOLD = "1"; - inline auto RESET_BOLD = "22"; - inline auto DIM = "2"; - inline auto RESET_DIM = "22"; - inline auto ITALIC = "3"; - inline auto RESET_ITALIC = "23"; - inline auto UNDERLINE = "4"; - inline auto RESET_UNDERLINE = "24"; - inline auto BLINKING = "5"; - inline auto RESET_BLINKING = "25"; - inline auto INVERSE = "7"; - inline auto RESET_INVERSE = "27"; - inline auto HIDDEN = "8"; - inline auto RESET_HIDDEN = "28"; - inline auto STRIKETHROUGH = "9"; - inline auto RESET_STRIKETHROUGH = "29"; - - inline auto COLOR_DEFAULT = "39"; - inline auto BACKGROUND_DEFAULT = "49"; - - inline auto BLACK = "30"; - inline auto RED = "31"; - inline auto GREEN = "32"; - inline auto YELLOW = "33"; - inline auto BLUE = "34"; - inline auto MAGENTA = "35"; - inline auto CYAN = "36"; - inline auto WHITE = "37"; - inline auto BLACK_BACKGROUND = "40"; - inline auto RED_BACKGROUND = "41"; - inline auto GREEN_BACKGROUND = "42"; - inline auto YELLOW_BACKGROUND = "43"; - inline auto BLUE_BACKGROUND = "44"; - inline auto MAGENTA_BACKGROUND = "45"; - inline auto CYAN_BACKGROUND = "46"; - inline auto WHITE_BACKGROUND = "47"; - - inline auto BRIGHT_BLACK = "90"; - inline auto BRIGHT_RED = "91"; - inline auto BRIGHT_GREEN = "92"; - inline auto BRIGHT_YELLOW = "93"; - inline auto BRIGHT_BLUE = "94"; - inline auto BRIGHT_MAGENTA = "95"; - inline auto BRIGHT_CYAN = "96"; - inline auto BRIGHT_WHITE = "97"; - inline auto BRIGHT_BLACK_BACKGROUND = "100"; - inline auto BRIGHT_RED_BACKGROUND = "101"; - inline auto BRIGHT_GREEN_BACKGROUND = "102"; - inline auto BRIGHT_YELLOW_BACKGROUND = "103"; - inline auto BRIGHT_BLUE_BACKGROUND = "104"; - inline auto BRIGHT_MAGENTA_BACKGROUND = "105"; - inline auto BRIGHT_CYAN_BACKGROUND = "106"; - inline auto BRIGHT_WHITE_BACKGROUND = "107"; - - template - inline auto make_color(Args... colors) - { - std::string mode; - ((mode += std::string(colors) + ";"), ...); - return ESC("[" + mode.substr(0, mode.size() - 1) + "m"); - } - } - - enum class log_level - { - // default - NONE, - // low level - TRACE0, TRACE1, TRACE2, TRACE3, - // normal - TRACE, DEBUG, INFO, - // errors - WARN, ERROR, FATAL, - }; - - struct tag_func_param - { - blt::logging::log_level level; - const std::string& file, line, raw_string, formatted_string; - }; - - struct tag - { - // tag without the ${{ or }} - std::string tag; - // function to run: log level, file, line, and raw user input string are provided - std::function func; - }; - - struct log_format - { - /** - * the log output format is the string which will be used to create the log output string - * - * Available tags: - * - ${{YEAR}} // current year - * - ${{MONTH}} // current month - * - ${{DAY}} // current day - * - ${{HOUR}} // current hour - * - ${{MINUTE}} // current minute - * - ${{SECOND}} // current second - * - ${{MS}} // current unix time - * - ${{NS}} // current ns from the high resolution system timer - * - ${{ISO_YEAR}} // ISO formatted 'year-month-day' in a single variable - * - ${{TIME}} // 'hour:minute:second' formatted string in a single variable - * - ${{FULL_TIME}} // 'year-month-day hour:minute:second' in a single variable - * - ${{LF}} // log level color (ANSI color code) - * - ${{ER}} // Error red - * - ${{CNR}} // conditional error red (only outputs if log level is an error!) - * - ${{RC}} // ANSI color reset - * - ${{LOG_LEVEL}} // current log level - * - ${{THREAD_NAME}} // current thread name, NOTE: thread names must be set by calling "setThreadName()" from the thread in question! - * - ${{FILE}} // file from which the macro is invoked - * - ${{LINE}} // line in the from which the macro is invoked - * - ${{RAW_STR}} // raw user string without formatting applied (NOTE: format args are not provided!) - * - ${{STR}} // the user supplied string (format applied!) - */ - std::string logOutputFormat = "\033[94m[${{TIME}}]${{RC}} ${{LF}}[${{LOG_LEVEL}}]${{RC}} \033[35m(${{FILE}}:${{LINE}})${{RC}} ${{CNR}}${{STR}}${{RC}}\n"; - std::string levelNames[11] = {"STDOUT", "TRACE0", "TRACE1", "TRACE2", "TRACE3", "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"}; - std::string levelColors[11] = {"\033[0m", "\033[22;97m", "\033[97m", "\033[97m", "\033[97m", "\033[97m", "\033[36m", "\033[92m", "\033[93m", - "\033[91m", "\033[97;41m"}; - // if true prints the whole path to the file (eg /home/user/.../.../project/src/source.cpp:line#) - bool printFullFileName = false; - // the logging lib will keep track of the largest line found so far and try to keep the spacing accordingly - // this is not thread safe! - bool ensureAlignment = false; - // should we log to file? - bool logToFile = false; - // should we log to console? - bool logToConsole = true; - // where should we log? (empty for current binary directory) should end in a / if not empty! - std::string logFilePath; - // logs to a file called $fileName_$count.log where count is the number of rollover files - // this accepts any of the macros above, using level names and colors should work but isn't supported. - std::string logFileName = "${{ISO_YEAR}}"; - // default limit on file size: 10mb; - size_t logMaxFileSize = 1024 * 1024 * 10; - /** - * Variables below this line should never be changed by the user! - */ - // the current alignment width found (you shouldn't chance this variable!) - size_t currentWidth = 0; - // current number of file roll-overs. you shouldn't change this either. - size_t currentRollover = 0; - std::string lastFile; - }; - - struct logger - { - log_level level; - const char* file; - int line; - }; - - struct empty_logger - { - - }; - - void log_internal(const std::string& format, log_level level, const char* file, int line, std::va_list& args); - - void log_stream_internal(const std::string& str, const logger& logger); - - template - inline std::string to_string_stream(const T& t) - { - std::stringstream stream; - stream << t; - return stream.str(); - } - - template - inline static void log_stream(const T& t, const logger& logger) - { - if constexpr (std::is_arithmetic_v && !std::is_same_v) - { - log_stream_internal(std::to_string(t), logger); - } else if constexpr (std::is_same_v || std::is_same_v) - { - log_stream_internal(t, logger); - } else - { - log_stream_internal(to_string_stream(t), logger); - } - } - - template - inline void log(T t, log_level level, const char* file, int line, ...) - { - std::va_list args; - va_start(args, line); - if constexpr (std::is_arithmetic_v) - { - log_internal(std::to_string(t), level, file, line, args); - } else if constexpr (std::is_same_v) - { - log_internal(t, level, file, line, args); - } else if constexpr (std::is_same_v) - { - log_internal(std::string(t), level, file, line, args); - } else - { - log_internal(to_string_stream(t), level, file, line, args); - } - va_end(args); - } - - template - static inline const blt::logging::logger& operator<<(const blt::logging::logger& out, const T& t) - { - log_stream(t, out); - return out; - } - - template - static inline const blt::logging::empty_logger& operator<<(const blt::logging::empty_logger& out, const T&) - { - return out; - } - - void flush(); - - void newline(); - - void setThreadName(const std::string& name); - - void setLogFormat(const log_format& format); - - void setLogColor(log_level level, const std::string& newFormat); - - void setLogName(log_level level, const std::string& newFormat); - - void setLogOutputFormat(const std::string& newFormat); - - void setLogToFile(bool shouldLogToFile); - - void setLogToConsole(bool shouldLogToConsole); - - void setLogPath(const std::string& path); - - void setLogFileName(const std::string& fileName); - - void setMaxFileSize(size_t fileSize); -} - -//#define BLT_LOGGING_IMPLEMENTATION -#ifdef BLT_LOGGING_IMPLEMENTATION - - #include - #include - #include - #include - #include - #include - #include - #include - #if defined(CXX17_FILESYSTEM) || defined (CXX17_FILESYSTEM_LIBFS) - #include - #elif defined(CXX11_EXP_FILESYSTEM) || defined (CXX11_EXP_FILESYSTEM_LIBFS) - #include - #else - #include - #endif - #include - #include - -template -using hashmap = std::unordered_map; - -namespace blt::logging { - -/** - * Used to store fast associations between built in tags and their respective values - */ -class tag_map { - private: - tag* tags; - size_t size; - - [[nodiscard]] static inline size_t hash(const tag& t) { - size_t h = t.tag[1] * 3 - t.tag[0]; - return h - 100; - } - - // TODO: fix - void expand() { - auto newSize = size * 2; - auto newTags = new tag[newSize]; - for (size_t i = 0; i < size; i++) - newTags[i] = tags[i]; - delete[] tags; - tags = newTags; - size = newSize; - } - public: - tag_map(std::initializer_list initial_tags){ - size_t max = 0; - for (const auto& t : initial_tags) - max = std::max(max, hash(t)); - tags = new tag[(size = max+1)]; - for (const auto& t : initial_tags) - insert(t); - } - tag_map(const tag_map& copy) { - tags = new tag[(size = copy.size)]; - for (size_t i = 0; i < size; i++) - tags[i] = copy.tags[i]; - } - - void insert(const tag& t) { - auto h = hash(t); - //if (h > size) - // expand(); - if (!tags[h].tag.empty()) - std::cerr << "Tag not empty! " << tags[h].tag << "!!!\n"; - tags[h] = t; - } - - tag& operator[](const std::string& name) const { - auto h = hash(tag{name, nullptr}); - if (h > size) - std::cerr << "Tag out of bounds"; - return tags[h]; - } - - ~tag_map(){ - delete[] tags; - tags = nullptr; - } -}; - -class LogFileWriter { - private: - std::string m_path; - std::fstream* output = nullptr; - public: - explicit LogFileWriter() = default; - - void writeLine(const std::string& path, const std::string& line){ - if (path != m_path || output == nullptr){ - clear(); - delete output; - output = new std::fstream(path, std::ios::out | std::ios::app); - if (!output->good()){ - throw std::runtime_error("Unable to open console filestream!\n"); - } - } - if (!output->good()){ - std::cerr << "There has been an error in the logging file stream!\n"; - output->clear(); - } - *output << line; - } - - void clear(){ - if (output != nullptr) { - try { - output->flush(); - output->close(); - } catch (std::exception& e){ - std::cerr << e.what() << "\n"; - } - } - } - - ~LogFileWriter() { - clear(); - delete(output); - } -}; - -#ifdef WIN32 - #define BLT_NOW() auto t = std::time(nullptr); tm now{}; localtime_s(&now, &t) -#else - #define BLT_NOW() auto t = std::time(nullptr); auto now_ptr = std::localtime(&t); auto& now = *now_ptr -#endif - -//#define BLT_NOW() auto t = std::time(nullptr); tm now; localtime_s(&now, &t); //auto now = std::localtime(&t) - #define BLT_ISO_YEAR(S) auto S = std::to_string(now.tm_year + 1900); \ - S += '-'; \ - S += ensureHasDigits(now.tm_mon+1, 2); \ - S += '-'; \ - S += ensureHasDigits(now.tm_mday, 2); - #define BLT_CUR_TIME(S) auto S = ensureHasDigits(now.tm_hour, 2); \ - S += ':'; \ - S += ensureHasDigits(now.tm_min, 2); \ - S += ':'; \ - S += ensureHasDigits(now.tm_sec, 2); - -static inline std::string ensureHasDigits(int current, int digits) { - std::string asString = std::to_string(current); - auto length = digits - asString.length(); - if (length <= 0) - return asString; - std::string zeros; - zeros.reserve(length); - for (unsigned int i = 0; i < length; i++){ - zeros += '0'; - } - return zeros + asString; -} - -log_format loggingFormat {}; -hashmap loggingThreadNames; -hashmap> loggingStreamLines; -LogFileWriter writer; - -const std::unique_ptr tagMap = std::make_unique(tag_map{ - {"YEAR", [](const tag_func_param&) -> std::string { - BLT_NOW(); - return std::to_string(now.tm_year); - }}, - {"MONTH", [](const tag_func_param&) -> std::string { - BLT_NOW(); - return ensureHasDigits(now.tm_mon+1, 2); - }}, - {"DAY", [](const tag_func_param&) -> std::string { - BLT_NOW(); - return ensureHasDigits(now.tm_mday, 2); - }}, - {"HOUR", [](const tag_func_param&) -> std::string { - BLT_NOW(); - return ensureHasDigits(now.tm_hour, 2); - }}, - {"MINUTE", [](const tag_func_param&) -> std::string { - BLT_NOW(); - return ensureHasDigits(now.tm_min, 2); - }}, - {"SECOND", [](const tag_func_param&) -> std::string { - BLT_NOW(); - return ensureHasDigits(now.tm_sec, 2); - }}, - {"MS", [](const tag_func_param&) -> std::string { - return std::to_string(std::chrono::duration_cast( - std::chrono::high_resolution_clock::now().time_since_epoch()).count() - ); - }}, - {"NS", [](const tag_func_param&) -> std::string { - return std::to_string(std::chrono::duration_cast( - std::chrono::high_resolution_clock::now().time_since_epoch()).count() - ); - }}, - {"ISO_YEAR", [](const tag_func_param&) -> std::string { - BLT_NOW(); - BLT_ISO_YEAR(returnStr); - return returnStr; - }}, - {"TIME", [](const tag_func_param&) -> std::string { - BLT_NOW(); - BLT_CUR_TIME(returnStr); - return returnStr; - }}, - {"FULL_TIME", [](const tag_func_param&) -> std::string { - BLT_NOW(); - BLT_ISO_YEAR(ISO); - BLT_CUR_TIME(TIME); - ISO += ' '; - ISO += TIME; - return ISO; - }}, - {"LF", [](const tag_func_param& f) -> std::string { - return loggingFormat.levelColors[(int)f.level]; - }}, - {"ER", [](const tag_func_param&) -> std::string { - return loggingFormat.levelColors[(int)log_level::ERROR]; - }}, - {"CNR", [](const tag_func_param& f) -> std::string { - return f.level >= log_level::ERROR ? loggingFormat.levelColors[(int)log_level::ERROR] : ""; - }}, - {"RC", [](const tag_func_param&) -> std::string { - return "\033[0m"; - }}, - {"LOG_LEVEL", [](const tag_func_param& f) -> std::string { - return loggingFormat.levelNames[(int)f.level]; - }}, - {"THREAD_NAME", [](const tag_func_param&) -> std::string { - if (loggingThreadNames.find(std::this_thread::get_id()) == loggingThreadNames.end()) - return "UNKNOWN"; - return loggingThreadNames[std::this_thread::get_id()]; - }}, - {"FILE", [](const tag_func_param& f) -> std::string { - return f.file; - }}, - {"LINE", [](const tag_func_param& f) -> std::string { - return f.line; - }}, - {"RAW_STR", [](const tag_func_param& f) -> std::string { - return f.raw_string; - }}, - {"STR", [](const tag_func_param& f) -> std::string { - return f.formatted_string; - }} -}); - -static inline std::vector split(std::string s, const std::string& delim) { - size_t pos = 0; - std::vector tokens; - while ((pos = s.find(delim)) != std::string::npos) { - auto token = s.substr(0, pos); - tokens.push_back(token); - s.erase(0, pos + delim.length()); - } - tokens.push_back(s); - return tokens; -} - -inline std::string filename(const std::string& path){ - if (loggingFormat.printFullFileName) - return path; - auto paths = split(path, "/"); - auto final = paths[paths.size()-1]; - if (final == "/") - return paths[paths.size()-2]; - return final; -} - -class string_parser { - private: - std::string _str; - size_t _pos; - public: - explicit string_parser(std::string str): _str(std::move(str)), _pos(0) {} - - inline char next(){ - return _str[_pos++]; - } - - [[nodiscard]] inline bool has_next() const { - return _pos < _str.size(); - } -}; - -std::string stripANSI(const std::string& str){ - string_parser parser(str); - std::string out; - while (parser.has_next()){ - char c = parser.next(); - if (c == '\033'){ - while (parser.has_next() && parser.next() != 'm'); - } else - out += c; - } - return out; -} - -void applyCFormatting(const std::string& format, std::string& output, std::va_list& args){ - // args must be copied because they will be consumed by the first vsnprintf - va_list args_copy; - va_copy(args_copy, args); - - auto buffer_size = std::vsnprintf(nullptr, 0, format.c_str(), args_copy) + 1; - auto* buffer = new char[static_cast(buffer_size)]; - - vsnprintf(buffer, buffer_size, format.c_str(), args); - output = std::string(buffer); - - delete[] buffer; - - va_end(args_copy); -} - -/** - * Checks if the next character in the parser is a tag opening, if not output the buffer to the out string - */ -inline bool tagOpening(string_parser& parser, std::string& out){ - char c = ' '; - if (parser.has_next() && (c = parser.next()) == '{') - if (parser.has_next() && (c = parser.next()) == '{') - return true; - else - out += c; - else - out += c; - return false; -} - -void parseString(string_parser& parser, std::string& out, const std::string& userStr, log_level level, const char* file, int line){ - while (parser.has_next()){ - char c = parser.next(); - std::string nonTag; - if (c == '$' && tagOpening(parser, nonTag)){ - std::string tag; - while (parser.has_next()){ - c = parser.next(); - if (c == '}') - break; - tag += c; - } - c = parser.next(); - if (parser.has_next() && c != '}') { - std::cerr << "Error processing tag, is not closed with two '}'!\n"; - break; - } - if (loggingFormat.ensureAlignment && tag == "STR") { - auto currentOutputWidth = out.size(); - auto& longestWidth = loggingFormat.currentWidth; - longestWidth = std::max(longestWidth, currentOutputWidth); - // pad with spaces - if (currentOutputWidth != longestWidth){ - for (size_t i = currentOutputWidth; i < longestWidth; i++) - out += ' '; - } - } - tag_func_param param{ - level, filename({file}), std::to_string(line), userStr, userStr - }; - out += (*tagMap)[tag].func(param); - } else { - out += c; - out += nonTag; - } - } -} - -std::string applyFormatString(const std::string& str, log_level level, const char* file, int line){ - // this can be speedup by preprocessing the string into an easily callable class - // where all the variables are ready to be substituted in one step - // and all static information already entered - string_parser parser(loggingFormat.logOutputFormat); - std::string out; - parseString(parser, out, str, level, file, line); - - return out; -} - -void log_internal(const std::string& format, log_level level, const char* file, int line, std::va_list& args) { - std::string withoutLn = format; - auto len = withoutLn.length(); - - if (len > 0 && withoutLn[len - 1] == '\n') - withoutLn = withoutLn.substr(0, len-1); - - std::string out; - - applyCFormatting(withoutLn, out, args); - - if (level == log_level::NONE){ - std::cout << out << std::endl; - return; - } - - std::string finalFormattedOutput = applyFormatString(out, level, file, line); - - if (loggingFormat.logToConsole) - std::cout << finalFormattedOutput; - - - if (loggingFormat.logToFile){ - string_parser parser(loggingFormat.logFileName); - std::string fileName; - parseString(parser, fileName, withoutLn, level, file, line); - - auto path = loggingFormat.logFilePath; - if (!path.empty() && path[path.length()-1] != '/') - path += '/'; - - // if the file has changed (new day in default case) we should reset the rollover count - if (loggingFormat.lastFile != fileName){ - loggingFormat.currentRollover = 0; - loggingFormat.lastFile = fileName; - } - - path += fileName; - path += '-'; - path += std::to_string(loggingFormat.currentRollover); - path += ".log"; - - if (std::filesystem::exists(path)) { - auto fileSize = std::filesystem::file_size(path); - - // will start on next file - if (fileSize > loggingFormat.logMaxFileSize) - loggingFormat.currentRollover++; - } - - writer.writeLine(path, stripANSI(finalFormattedOutput)); - } - //std::cout.flush(); -} - -void log_stream_internal(const std::string& str, const logger& logger) { - auto& s = loggingStreamLines[std::this_thread::get_id()][logger.level]; -// s += str; - for (char c : str){ - s += c; - if (c == '\n'){ - log(s, logger.level, logger.file, logger.line); - s = ""; - } - } -} - -void setThreadName(const std::string& name) { - loggingThreadNames[std::this_thread::get_id()] = name; -} - -void setLogFormat(const log_format& format){ - loggingFormat = format; -} -void setLogColor(log_level level, const std::string& newFormat){ - loggingFormat.levelColors[(int)level] = newFormat; -} -void setLogName(log_level level, const std::string& newFormat){ - loggingFormat.levelNames[(int)level] = newFormat; -} -void setLogOutputFormat(const std::string& newFormat){ - loggingFormat.logOutputFormat = newFormat; -} -void setLogToFile(bool shouldLogToFile){ - loggingFormat.logToFile = shouldLogToFile; -} -void setLogToConsole(bool shouldLogToConsole){ - loggingFormat.logToConsole = shouldLogToConsole; -} -void setLogPath(const std::string& path){ - loggingFormat.logFilePath = path; -} -void setLogFileName(const std::string& fileName){ - loggingFormat.logFileName = fileName; -} -void setMaxFileSize(size_t fileSize) { - loggingFormat.logMaxFileSize = fileSize; -} - -void flush() { - std::cerr.flush(); - std::cout.flush(); -} - -void newline() -{ - std::cout << std::endl; -} - -} - -#endif - -#if defined(__clang__) || defined(__llvm__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" -#endif - -#ifdef BLT_DISABLE_LOGGING - #define BLT_LOG(format, level, ...) - #define BLT_LOG_STREAM(level) - #define BLT_TRACE0_STREAM - #define BLT_TRACE1_STREAM - #define BLT_TRACE2_STREAM - #define BLT_TRACE3_STREAM - #define BLT_TRACE_STREAM - #define BLT_DEBUG_STREAM - #define BLT_INFO_STREAM - #define BLT_WARN_STREAM - #define BLT_ERROR_STREAM - #define BLT_FATAL_STREAM - #define BLT_TRACE(format, ...) - #define BLT_DEBUG(format, ...) - #define BLT_INFO(format, ...) - #define BLT_WARN(format, ...) - #define BLT_ERROR(format, ...) - #define BLT_FATAL(format, ...) -#else - #define BLT_NEWLINE() blt::logging::newline() - #define BLT_LOG(format, level, ...) blt::logging::log(format, level, __FILE__, __LINE__, ##__VA_ARGS__) - #define BLT_LOG_STREAM(level) blt::logging::logger{level, __FILE__, __LINE__} - #ifdef BLT_DISABLE_TRACE - #define BLT_TRACE(format, ...) - #define BLT_TRACE0_STREAM blt::logging::empty_logger{} - #define BLT_TRACE1_STREAM blt::logging::empty_logger{} - #define BLT_TRACE2_STREAM blt::logging::empty_logger{} - #define BLT_TRACE3_STREAM blt::logging::empty_logger{} - #define BLT_TRACE_STREAM blt::logging::empty_logger{} - #else - #define BLT_TRACE(format, ...) BLT_LOG(format, blt::logging::log_level::TRACE, ##__VA_ARGS__) - #define BLT_TRACE0_STREAM BLT_LOG_STREAM(blt::logging::log_level::TRACE0) - #define BLT_TRACE1_STREAM BLT_LOG_STREAM(blt::logging::log_level::TRACE1) - #define BLT_TRACE2_STREAM BLT_LOG_STREAM(blt::logging::log_level::TRACE2) - #define BLT_TRACE3_STREAM BLT_LOG_STREAM(blt::logging::log_level::TRACE3) - #define BLT_TRACE_STREAM BLT_LOG_STREAM(blt::logging::log_level::TRACE) - #endif - - #ifdef BLT_DISABLE_DEBUG - #define BLT_DEBUG(format, ...) - #define BLT_DEBUG_STREAM blt::logging::empty_logger{} - #else - #define BLT_DEBUG(format, ...) BLT_LOG(format, blt::logging::log_level::DEBUG, ##__VA_ARGS__) - #define BLT_DEBUG_STREAM BLT_LOG_STREAM(blt::logging::log_level::DEBUG) - #endif - - #ifdef BLT_DISABLE_INFO - #define BLT_INFO(format, ...) - #define BLT_INFO_STREAM blt::logging::empty_logger{} - #else - #define BLT_INFO(format, ...) BLT_LOG(format, blt::logging::log_level::INFO, ##__VA_ARGS__) - #define BLT_INFO_STREAM BLT_LOG_STREAM(blt::logging::log_level::INFO) - #endif - - #ifdef BLT_DISABLE_WARN - #define BLT_WARN(format, ...) - #define BLT_WARN_STREAM blt::logging::empty_logger{} - #else - #define BLT_WARN(format, ...) BLT_LOG(format, blt::logging::log_level::WARN, ##__VA_ARGS__) - #define BLT_WARN_STREAM BLT_LOG_STREAM(blt::logging::log_level::WARN) - #endif - - #ifdef BLT_DISABLE_ERROR - #define BLT_ERROR(format, ...) - #define BLT_ERROR_STREAM blt::logging::empty_logger{} - #else - #define BLT_ERROR(format, ...) BLT_LOG(format, blt::logging::log_level::ERROR, ##__VA_ARGS__) - #define BLT_ERROR_STREAM BLT_LOG_STREAM(blt::logging::log_level::ERROR) - #endif - - #ifdef BLT_DISABLE_FATAL - #define BLT_FATAL(format, ...) - #define BLT_FATAL_STREAM blt::logging::empty_logger{} - #else - #define BLT_FATAL(format, ...) BLT_LOG(format, blt::logging::log_level::FATAL, ##__VA_ARGS__) - #define BLT_FATAL_STREAM BLT_LOG_STREAM(blt::logging::log_level::FATAL) - #endif -#endif - -#if defined(__clang__) || defined(__llvm__) -#pragma clang diagnostic pop -#endif - -#endif //BLT_TESTS_LOGGING2_H diff --git a/include/blt/std/memory.h b/include/blt/std/memory.h index 597f31c..47ffd84 100644 --- a/include/blt/std/memory.h +++ b/include/blt/std/memory.h @@ -33,13 +33,11 @@ namespace blt { public: using element_type = T; - using value_type = typename std::iterator_traits::value_type; - using pointer = typename std::iterator_traits::pointer; - using const_pointer = const typename std::iterator_traits::pointer; - using reference = typename std::iterator_traits::reference; - using const_reference = const typename std::iterator_traits::reference; - using difference_type = typename std::iterator_traits::difference_type; - using iterator_category = typename std::iterator_traits::iterator_category; + using value_type = std::remove_cv_t; + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; using iterator = ptr_iterator; using const_iterator = ptr_iterator; diff --git a/include/blt/std/memory_util.h b/include/blt/std/memory_util.h index 2c73912..c2041a4 100644 --- a/include/blt/std/memory_util.h +++ b/include/blt/std/memory_util.h @@ -134,7 +134,7 @@ namespace blt::mem return fromBytes(in, *out); } - static std::size_t next_byte_allocation(std::size_t prev_size, std::size_t default_allocation_block = 8192, std::size_t default_size = 16) + inline std::size_t next_byte_allocation(std::size_t prev_size, std::size_t default_allocation_block = 8192, std::size_t default_size = 16) { if (prev_size < default_size) return default_size; diff --git a/include/blt/std/mmap.h b/include/blt/std/mmap.h index e1b7810..c0fb7ee 100644 --- a/include/blt/std/mmap.h +++ b/include/blt/std/mmap.h @@ -19,9 +19,11 @@ #ifndef BLT_MMAP_H #define BLT_MMAP_H -#include #include #include +#include +#include +#include // size of 2mb in bytes inline constexpr blt::size_t BLT_2MB_SIZE = 2048 * 1024; @@ -41,7 +43,7 @@ namespace blt public: bad_alloc_t() = default; - explicit bad_alloc_t(std::string_view str): str(str) + explicit bad_alloc_t(const std::string_view str): str(str) {} explicit bad_alloc_t(std::string str): str(std::move(str)) diff --git a/include/blt/std/thread.h b/include/blt/std/thread.h index 4ee56f9..559bd78 100644 --- a/include/blt/std/thread.h +++ b/include/blt/std/thread.h @@ -31,7 +31,7 @@ #include #include #include -#include +#include #include namespace blt diff --git a/libraries/parallel-hashmap b/libraries/parallel-hashmap index 8a889d3..154c634 160000 --- a/libraries/parallel-hashmap +++ b/libraries/parallel-hashmap @@ -1 +1 @@ -Subproject commit 8a889d3699b3c09ade435641fb034427f3fd12b6 +Subproject commit 154c63489e84d5569d3b466342a2ae8fd99e4734 diff --git a/src/blt/format/format.cpp b/src/blt/format/format.cpp index 3d79234..bf15857 100644 --- a/src/blt/format/format.cpp +++ b/src/blt/format/format.cpp @@ -6,7 +6,7 @@ #include #include #include -#include "blt/std/logging.h" +#include "blt/logging/logging.h" #include "blt/std/assert.h" #include "blt/std/utility.h" #include diff --git a/src/blt/fs/file_writers.cpp b/src/blt/fs/file_writers.cpp new file mode 100644 index 0000000..2bc5b94 --- /dev/null +++ b/src/blt/fs/file_writers.cpp @@ -0,0 +1,158 @@ +/* + * + * 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 +#include +#include +#include +#include +#include + +namespace blt::fs +{ + i64 fwriter_t::write(const char* buffer, const size_t bytes) + { + return static_cast(std::fwrite(buffer, 1, bytes, m_file)); + } + + void fwriter_t::flush() + { + writer_t::flush(); + std::fflush(m_file); + } + + void fwriter_t::newfile(const std::string& new_name) + { + if (m_file != nullptr) + std::fclose(m_file); + m_file = std::fopen(new_name.c_str(), m_mode.c_str()); + if (!m_file) + throw std::runtime_error("Failed to open file for writing"); + } + + buffered_writer::buffered_writer(const std::string& name, const size_t buffer_size): fwriter_t{name, "ab"} + { + m_buffer.resize(buffer_size); + } + + buffered_writer::buffered_writer(const size_t buffer_size) + { + m_buffer.resize(buffer_size); + } + + void buffered_writer::newfile(const std::string& new_name) + { + fwriter_t::newfile(new_name); + setvbuf(this->m_file, nullptr, _IONBF, 0); + } + + i64 buffered_writer::write(const char* buffer, const size_t bytes) + { + if (bytes > m_buffer.size()) + return fwriter_t::write(buffer, bytes); + if (bytes + m_current_pos > m_buffer.size()) + flush(); + std::memcpy(m_buffer.data() + m_current_pos, buffer, bytes); + m_current_pos += bytes; + return static_cast(bytes); + } + + void buffered_writer::flush() + { + fwriter_t::write(m_buffer.data(), m_current_pos); + fwriter_t::flush(); + m_current_pos = 0; + } + + bounded_writer::bounded_writer(fwriter_t& writer, std::optional base_name, const size_t max_size, naming_function_t naming_function): + m_writer(&writer), m_base_name(std::move(base_name)), m_max_size(max_size), m_naming_function(std::move(naming_function)) + { + bounded_writer::newfile(m_base_name.value_or("")); + } + + i64 bounded_writer::write(const char* buffer, const size_t bytes) + { + m_currently_written += bytes; + if (m_currently_written > m_max_size) + this->newfile(m_base_name.value_or("")); + return m_writer->write(buffer, bytes); + } + + void bounded_writer::newfile(const std::string& new_name) + { + ++m_current_invocation; + m_currently_written = 0; + m_writer->newfile(m_naming_function(m_current_invocation, new_name)); + } + + void bounded_writer::flush() + { + m_writer->flush(); + } + + rotating_writer::rotating_writer(fwriter_t& writer, const time_t period): m_writer{&writer}, m_period{period} + { + newfile(); + } + + i64 rotating_writer::write(const char* buffer, const size_t bytes) + { + check_for_time(); + return m_writer->write(buffer, bytes); + } + + void rotating_writer::flush() + { + check_for_time(); + m_writer->flush(); + } + + void rotating_writer::newfile(const std::string& new_name) + { + m_writer->newfile(new_name); + } + + void rotating_writer::newfile() + { + m_last_time = get_current_time(); + std::string name; + name += std::to_string(m_last_time.year); + name += "-" + std::to_string(m_last_time.month); + name += "-" + std::to_string(m_last_time.day); + if (m_period.hour >= 0) + name += "-" + std::to_string(m_last_time.hour); + name += ".txt"; + newfile(name); + } + + void rotating_writer::check_for_time() + { + const auto current_time = get_current_time(); + if ((m_period.hour > 0 && current_time.hour > m_last_time.hour + m_period.hour) || + (m_period.day > 0 && current_time.day > m_last_time.day + m_period.day) || + (m_period.month > 0 && current_time.month > m_last_time.month + m_period.month) || + (m_period.year > 0 && current_time.year > m_last_time.year + m_period.year)) + newfile(); + } + + time_t rotating_writer::get_current_time() + { + const std::time_t time = std::time(nullptr); + const auto current_time = std::localtime(&time); + return {current_time->tm_year + 1900, current_time->tm_mon + 1, current_time->tm_mday, current_time->tm_hour}; + } +} diff --git a/src/blt/fs/filesystem.cpp b/src/blt/fs/filesystem.cpp index 7fa829b..9718d9b 100644 --- a/src/blt/fs/filesystem.cpp +++ b/src/blt/fs/filesystem.cpp @@ -17,63 +17,4 @@ */ #include #include -#include -namespace blt::fs -{ - - fstream_block_reader::fstream_block_reader(std::fstream& stream, size_t bufferSize): - block_reader(bufferSize), m_stream(stream), m_buffer(new char[bufferSize]) - { - if (!m_stream.good() || m_stream.fail()) - BLT_WARN("Provided std::fstream is not good! Clearing!"); - m_stream.clear(); - } - - int fstream_block_reader::read(char* buffer, size_t bytes) - { - if (readIndex == 0) - m_stream.read(m_buffer, (long) m_bufferSize); - if (readIndex + bytes >= m_bufferSize) - { - // copy out all the data from the current buffer - auto bytesLeft = m_bufferSize - readIndex; - memcpy(buffer, m_buffer + readIndex, bytesLeft); - readIndex = 0; - // now to prevent large scale reading in small blocks, we should just read the entire thing into the buffer. - m_stream.read(buffer + bytesLeft, (long) (bytes - bytesLeft)); - } else - { - // but in the case that the size of the data read is small, we should read in blocks and copy from that buffer - // that should be quicker since file operations are slow. - std::memcpy(buffer, m_buffer + readIndex, bytes); - readIndex += bytes; - } - return 0; - } - - int fstream_block_writer::write(char* buffer, size_t bytes) - { - if (writeIndex + bytes >= m_bufferSize) - { - // in an attempt to stay efficient we write out the old buffer and the new buffer - // since there is a good chance there is more than a buffer's worth of data being written - // otherwise the buffer is almost full and can be written anyway. (this might be bad for performance especially if the FS wants round numbers) - m_stream.write(m_buffer, (long) writeIndex); - writeIndex = 0; - m_stream.write(buffer, (long) bytes); - } else - { - std::memcpy(m_buffer + writeIndex, buffer, bytes); - writeIndex += bytes; - } - return 0; - } - - void fstream_block_writer::flush_internal() - { - m_stream.write(m_buffer, (long) writeIndex); - writeIndex = 0; - } - -} \ No newline at end of file diff --git a/src/blt/fs/loader.cpp b/src/blt/fs/loader.cpp index c078630..fd39673 100644 --- a/src/blt/fs/loader.cpp +++ b/src/blt/fs/loader.cpp @@ -82,8 +82,8 @@ std::string blt::fs::getFile(std::string_view path) file_contents = file_stream.str(); } catch (std::ifstream::failure& e) { - BLT_WARN("Unable to read file '%s'!\n", std::string(path).c_str()); - BLT_WARN("Exception: %s", e.what()); + BLT_WARN("Unable to read file '{}'!\n", std::string(path).c_str()); + BLT_WARN("Exception: {}", e.what()); BLT_THROW(std::runtime_error("Failed to read file!\n")); } return file_contents; diff --git a/src/blt/fs/nbt.cpp b/src/blt/fs/nbt.cpp index e127f11..ad00e12 100644 --- a/src/blt/fs/nbt.cpp +++ b/src/blt/fs/nbt.cpp @@ -4,19 +4,19 @@ * See LICENSE file for license detail */ #include -#include +#include #include #include namespace blt::nbt { - void writeUTF8String(blt::fs::block_writer& stream, const std::string& str) { + void writeUTF8String(blt::fs::writer_t& stream, const std::string& str) { blt::string::utf8_string str8 = blt::string::createUTFString(str); stream.write(str8.characters, str8.size); delete[] str8.characters; } - std::string readUTF8String(blt::fs::block_reader& stream) { + std::string readUTF8String(blt::fs::reader_t& stream) { int16_t utflen; readData(stream, utflen); @@ -33,9 +33,10 @@ namespace blt::nbt { } void NBTReader::read() { - char t = reader.get(); + char t; + reader.read(&t, 1); if (t != (char)nbt_tag::COMPOUND) { - BLT_WARN("Found %d", t); + BLT_WARN("Found {:d}", t); throw std::runtime_error("Incorrectly formatted NBT data! Root tag must be a compound tag!"); } root = new tag_compound; diff --git a/src/blt/fs/path_helper.cpp b/src/blt/fs/path_helper.cpp new file mode 100644 index 0000000..bb2e210 --- /dev/null +++ b/src/blt/fs/path_helper.cpp @@ -0,0 +1,67 @@ +/* + * + * 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 +#include +#include + +namespace blt::fs +{ +#ifdef BLT_WINDOWS + constexpr static char delim = '\\'; +#else + constexpr static char delim = '/'; +#endif + + std::string base_name(const std::string& str) + { + return std::string(base_name_sv(str)); + } + + std::string_view base_name_sv(const std::string_view str) + { + const auto parts = string::split_sv(str, delim); + const auto file_parts = string::split_sv(parts.back(), '.'); + return file_parts.front(); + } + + std::string filename(const std::string& str) + { + return std::string(filename_sv(str)); + } + + std::string_view filename_sv(const std::string_view str) + { + const auto parts = string::split_sv(str, delim); + return parts.back(); + } + + std::string extension(const std::string& str) + { + return std::string(extension_sv(str)); + } + + std::string_view extension_sv(const std::string_view str) + { + const auto parts = string::split_sv(str, delim); + const auto file_parts = parts.back().find_first_of('.'); + return parts.back().substr(std::min(file_parts + 1, parts.back().size())); + } +} + + + diff --git a/src/blt/fs/stream_wrappers.cpp b/src/blt/fs/stream_wrappers.cpp new file mode 100644 index 0000000..144001c --- /dev/null +++ b/src/blt/fs/stream_wrappers.cpp @@ -0,0 +1,45 @@ +/* + * + * 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 +#include +#include + +namespace blt::fs +{ + fstream_reader_t::fstream_reader_t(std::istream& stream): m_stream{&stream} + {} + + i64 fstream_reader_t::read(char* buffer, const size_t bytes) + { + return m_stream->readsome(buffer, static_cast(bytes)); + } + + fstream_writer_t::fstream_writer_t(std::ostream& stream): m_stream{&stream} + {} + + i64 fstream_writer_t::write(const char* buffer, const size_t bytes) + { + m_stream->write(buffer, static_cast(bytes)); + return static_cast(bytes); + } + + void fstream_writer_t::flush() + { + m_stream->flush(); + } +} diff --git a/src/blt/fs/threaded_wrtiers.cpp b/src/blt/fs/threaded_wrtiers.cpp new file mode 100644 index 0000000..7a5cba6 --- /dev/null +++ b/src/blt/fs/threaded_wrtiers.cpp @@ -0,0 +1,36 @@ +/* + * + * 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::fs +{ + concurrent_file_writer::concurrent_file_writer(writer_t* writer): m_writer{writer} + {} + + i64 concurrent_file_writer::write(const char* buffer, const size_t bytes) + { + std::scoped_lock lock{m_mutex}; + return m_writer->write(buffer, bytes); + } + + void concurrent_file_writer::flush() + { + std::scoped_lock lock{m_mutex}; + m_writer->flush(); + } +} \ No newline at end of file diff --git a/src/blt/logging/ansi.cpp b/src/blt/logging/ansi.cpp new file mode 100644 index 0000000..6c2e611 --- /dev/null +++ b/src/blt/logging/ansi.cpp @@ -0,0 +1,18 @@ +/* + * + * 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 \ No newline at end of file diff --git a/src/blt/logging/fmt_tokenizer.cpp b/src/blt/logging/fmt_tokenizer.cpp new file mode 100644 index 0000000..9ff97b5 --- /dev/null +++ b/src/blt/logging/fmt_tokenizer.cpp @@ -0,0 +1,593 @@ +/* + * + * 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 +#include +#include +#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; + 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; + case '{': + return fmt_token_type::OPEN_BRACKET; + case '}': + return fmt_token_type::CLOSE_BRACKET; + default: + return fmt_token_type::STRING; + } + } + + std::optional fmt_tokenizer_t::next() + { + if (m_pos >= m_fmt.size()) + return {}; + bool is_escaped = false; + if (m_fmt[m_pos] == '\\') + { + is_escaped = true; + ++m_pos; + } + switch (const auto base_type = get_type(m_fmt[m_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: + case fmt_token_type::POUND: + case fmt_token_type::LEFT_CHEVRON: + case fmt_token_type::RIGHT_CHEVRON: + case fmt_token_type::CARET: + case fmt_token_type::OPEN_BRACKET: + case fmt_token_type::CLOSE_BRACKET: + if (is_escaped) + return fmt_token_t{fmt_token_type::STRING, std::string_view{m_fmt.data() + m_pos++, 1}}; + return fmt_token_t{base_type, std::string_view{m_fmt.data() + m_pos++, 1}}; + default: + { + const auto begin = m_pos; + for (; m_pos < m_fmt.size() && get_type(m_fmt[m_pos]) == base_type; ++m_pos) + { + } + return fmt_token_t{base_type, std::string_view{m_fmt.data() + begin, m_pos - begin}}; + } + } + } + + std::vector fmt_tokenizer_t::tokenize(const std::string_view fmt) + { + m_fmt = fmt; + m_pos = 0; + std::vector tokens; + while (auto token = next()) + tokens.push_back(*token); + return tokens; + } + + const fmt_spec_t& fmt_parser_t::parse(const std::string_view fmt) + { + m_spec = {}; + m_pos = 0; + m_tokens = m_tokenizer.tokenize(fmt); + + parse_fmt_field(); + + 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()) + throw std::runtime_error("Expected token when parsing format field"); + const auto [type, value] = peek(); + switch (type) + { + case fmt_token_type::NUMBER: + parse_arg_id(); + if (has_next()) + { + if (peek().type == fmt_token_type::COLON) + parse_fmt_spec(); + else + throw std::runtime_error("Expected ':' when parsing format field after arg id!"); + } + break; + case fmt_token_type::COLON: + parse_fmt_spec(); + break; + default: + { + 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(); + } + + void fmt_parser_t::parse_arg_id() + { + const auto [type, value] = next(); + if (type != fmt_token_type::NUMBER) + { + 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)); + } + + std::string fmt_parser_t::parse_arg_or_number() + { + auto [type, value] = next(); + if (type == fmt_token_type::NUMBER) + return std::string(value); + if (type == fmt_token_type::OPEN_BRACKET) + { + auto [next_type, next_value] = next(); + if (next_type != fmt_token_type::NUMBER) + throw std::runtime_error("Expected number when parsing arg or number, unexpected value '" + std::string(next_value) + '\''); + if (next().type != fmt_token_type::CLOSE_BRACKET) + throw std::runtime_error("Expected closing bracket when parsing arg or number, unexpected value '" + std::string(next_value) + '\''); + // TODO: this feels like an evil hack. + const auto arg_id = std::stoul(std::string(next_value)); + if (arg_id >= m_handlers.size()) + throw std::runtime_error("Invalid arg id " + std::to_string(arg_id) + ", max arg supported: " + std::to_string(m_handlers.size())); + std::stringstream ss; + m_handlers[arg_id](ss, fmt_spec_t{}); + return ss.str(); + } + throw std::runtime_error("Expected number when parsing arg or number, unexpected value '" + std::string(value) + '\''); + } + + void fmt_parser_t::parse_fmt_spec() + { + // consume : + consume(); + auto [type, value] = peek(); + switch (type) + { + case fmt_token_type::STRING: + // if there is a token beyond this string, it is not a type string + if (has_next(1)) + parse_fmt_spec_fill(); + 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: + case fmt_token_type::CLOSE_BRACKET: + { + std::stringstream ss; + ss << "(Stage (Begin)) Invalid token type " << static_cast(type) << " value " << value; + throw std::runtime_error(ss.str()); + } + case fmt_token_type::NUMBER: + case fmt_token_type::OPEN_BRACKET: + 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; + } + } + + void fmt_parser_t::parse_fmt_spec_fill() + { + parse_fill(); + if (!has_next()) + return; + auto [type, value] = peek(); + switch (type) + { + 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::NUMBER: + case fmt_token_type::OPEN_BRACKET: + 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::STRING: + return; + case fmt_token_type::COLON: + case fmt_token_type::CLOSE_BRACKET: + { + std::stringstream ss; + ss << "(Stage (Fill)) Invalid token type " << static_cast(type) << " value " << value; + throw std::runtime_error(ss.str()); + } + } + } + + 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: + case fmt_token_type::OPEN_BRACKET: + 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: + case fmt_token_type::CLOSE_BRACKET: + { + std::stringstream ss; + ss << "(Stage (Align)) 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() + { + parse_sign(); + if (!has_next()) + return; + auto [type, value] = peek(); + switch (type) + { + case fmt_token_type::STRING: + return; + case fmt_token_type::SPACE: + 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: + case fmt_token_type::CLOSE_BRACKET: + { + std::stringstream ss; + ss << "(Stage (Sign)) Invalid token type " << static_cast(type) << " value " << value; + throw std::runtime_error(ss.str()); + } + case fmt_token_type::NUMBER: + case fmt_token_type::OPEN_BRACKET: + parse_fmt_spec_width(); + break; + case fmt_token_type::DOT: + parse_fmt_spec_precision(); + break; + case fmt_token_type::POUND: + parse_fmt_spec_form(); + break; + } + } + + void fmt_parser_t::parse_fmt_spec_form() + { + parse_form(); + if (!has_next()) + return; + auto [type, value] = peek(); + switch (type) + { + case fmt_token_type::STRING: + return; + case fmt_token_type::SPACE: + case fmt_token_type::COLON: + 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: + case fmt_token_type::CLOSE_BRACKET: + { + std::stringstream ss; + ss << "(Stage (Form)) Invalid token type " << static_cast(type) << " value " << value; + throw std::runtime_error(ss.str()); + } + case fmt_token_type::NUMBER: + case fmt_token_type::OPEN_BRACKET: + parse_fmt_spec_width(); + break; + case fmt_token_type::DOT: + parse_fmt_spec_precision(); + break; + } + } + + // handle width parsing + void fmt_parser_t::parse_fmt_spec_width() + { + parse_width(); + if (!has_next()) + return; + auto [type, value] = peek(); + switch (type) + { + case fmt_token_type::STRING: + return; + case fmt_token_type::COLON: + case fmt_token_type::MINUS: + case fmt_token_type::PLUS: + 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: + case fmt_token_type::OPEN_BRACKET: + case fmt_token_type::CLOSE_BRACKET: + { + std::stringstream ss; + ss << "(Stage (Width)) Invalid token type " << static_cast(type) << " value " << value; + throw std::runtime_error(ss.str()); + } + case fmt_token_type::DOT: + parse_fmt_spec_precision(); + break; + } + } + + void fmt_parser_t::parse_fmt_spec_precision() + { + // consume . + consume(); + parse_precision(); + } + + void fmt_parser_t::parse_fill() + { + auto [type, value] = next(); + if (type != fmt_token_type::STRING) + { + std::stringstream ss; + ss << "Expected string when parsing fill, got " << static_cast(type) << " value " << value; + throw std::runtime_error(ss.str()); + } + m_spec.prefix_char = value.front(); + } + + void fmt_parser_t::parse_align() + { + auto [type, value] = next(); + switch (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(type) << " value " << value; + throw std::runtime_error(ss.str()); + } + } + } + + void fmt_parser_t::parse_sign() + { + auto [_, value] = next(); + if (value.size() > 1) + { + 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]) + { + case '+': + m_spec.sign = fmt_sign_t::PLUS; + break; + case '-': + m_spec.sign = fmt_sign_t::MINUS; + break; + case ' ': + m_spec.sign = fmt_sign_t::SPACE; + break; + default: + { + std::stringstream ss; + ss << "Invalid sign " << value[0]; + throw std::runtime_error(ss.str()); + } + } + } + + void fmt_parser_t::parse_form() + { + consume(); + m_spec.alternate_form = true; + } + + void fmt_parser_t::parse_width() + { + const auto value = parse_arg_or_number(); + if (value.front() == '0' && !m_spec.prefix_char.has_value()) + m_spec.prefix_char = '0'; + m_spec.width = std::stoll(value); + } + + void fmt_parser_t::parse_precision() + { + if (!has_next()) + throw std::runtime_error("Missing token when parsing precision"); + auto value = parse_arg_or_number(); + m_spec.precision = std::stoll(std::string(value)); + } + + void fmt_parser_t::parse_type() + { + auto [_, value] = next(); + if (value.size() != 1) + { + 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()) + { + case 'b': + case 'B': + m_spec.type = fmt_type_t::BINARY; + break; + case 'c': + m_spec.type = fmt_type_t::CHAR; + break; + case 'd': + m_spec.type = fmt_type_t::DECIMAL; + break; + case 'o': + m_spec.type = fmt_type_t::OCTAL; + break; + case 'x': + case 'X': + m_spec.type = fmt_type_t::HEX; + break; + case 'a': + case 'A': + m_spec.type = fmt_type_t::HEX_FLOAT; + break; + case 'e': + case 'E': + m_spec.type = fmt_type_t::EXPONENT; + break; + case 'f': + case 'F': + m_spec.type = fmt_type_t::FIXED_POINT; + break; + case 'g': + case 'G': + m_spec.type = fmt_type_t::GENERAL; + break; + case 't': + case 'T': + m_spec.type = fmt_type_t::TYPE; + break; + default: + std::stringstream ss; + ss << "Invalid type " << value; + ss << std::endl << std::endl; + ss << "Expected one of: " << std::endl; + ss << std::setw(4) << ' ' << std::left << std::setw(5) << "b | B" << std::setw(6) << ' ' << "Print as binary output" << std::endl; + ss << std::setw(4) << ' ' << std::left << std::setw(5) << "c" << std::setw(6) << ' ' << "Print as character output" << std::endl; + ss << std::setw(4) << ' ' << std::left << std::setw(5) << "d" << std::setw(6) << ' ' << "Print as decimal (base 10) output" << std::endl; + ss << std::setw(4) << ' ' << std::left << std::setw(5) << "o" << std::setw(6) << ' ' << "Print as octal (base 8) output" << std::endl; + ss << std::setw(4) << ' ' << std::left << std::setw(5) << "x | X" << std::setw(6) << ' ' << "Print as hexadecimal (base 16) output" << + std::endl; + ss << std::setw(4) << ' ' << std::left << std::setw(5) << "a | A" << std::setw(6) << ' ' << "Print floats as hexadecimal (base 16) output" + << std::endl; + ss << std::setw(4) << ' ' << std::left << std::setw(5) << "e | E" << std::setw(6) << ' ' << "Print floats in scientific notation" << + std::endl; + ss << std::setw(4) << ' ' << std::left << std::setw(5) << "f | F" << std::setw(6) << ' ' << + "Print floats in fixed point output, useful for setting precision of output" << std::endl; + ss << std::setw(4) << ' ' << std::left << std::setw(5) << "g | G" << std::setw(6) << ' ' << + "Print floats in general output, switching between fixed point and scientific notation based on magnitude" << std::endl; + ss << std::setw(4) << ' ' << std::left << std::setw(5) << "t | T" << std::setw(6) << ' ' << "Print the type as a string" << std::endl; + throw std::runtime_error(ss.str()); + } + } +} diff --git a/src/blt/logging/logging.cpp b/src/blt/logging/logging.cpp new file mode 100644 index 0000000..4bc5cb2 --- /dev/null +++ b/src/blt/logging/logging.cpp @@ -0,0 +1,284 @@ +/* + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +namespace blt::logging +{ + struct global_context_t + { + logging_config_t global_config; + }; + + static global_context_t global_context; + + struct logging_thread_context_t + { + std::stringstream stream; + std::stringstream logging_stream; + std::string thread_name; + logger_t logger{stream}; + }; + + logging_thread_context_t& get_thread_context() + { + thread_local logging_thread_context_t context; + return context; + } + + std::string logger_t::to_string() const + { + return dynamic_cast(m_stream).str(); + } + + size_t logger_t::find_ending_brace(size_t begin) const + { + size_t braces = 0; + for (; begin < m_fmt.size(); ++begin) + { + if (m_fmt[begin] == '{') + ++braces; + else if (m_fmt[begin] == '}') + --braces; + if (braces == 0) + return begin; + } + return std::string::npos; + } + + void logger_t::setup_stream(const fmt_spec_t& spec) const + { + 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 + m_stream << std::setw(0); + if (spec.precision > 0) + m_stream << std::setprecision(static_cast(spec.precision)); + else + 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 + m_stream << std::nouppercase; + if (spec.sign == fmt_sign_t::PLUS) + m_stream << std::showpos; + else + m_stream << std::noshowpos; + } + + std::string logger_t::process_string(const std::string_view str) + { + auto result = std::string(str); + size_t pos = 0; + while (pos = result.find('{', pos), pos != std::string::npos) + { + if (pos > 0 && result[pos - 1] == '\\') + { + auto before = result.substr(0, pos - 1); + auto after = result.substr(pos); + result = before + after; + } else + ++pos; + } + return result; + } + + 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 << process_string(*str_it); + auto arg_pos = spec_it->arg_id; + if (arg_pos == -1) + arg_pos = static_cast(m_arg_pos++); + + setup_stream(*spec_it); + m_arg_print_funcs[arg_pos](m_stream, *spec_it); + } + m_stream << process_string(*str_it); + } + + void logger_t::handle_type(std::ostream& stream, const fmt_spec_t& spec) + { + switch (spec.type) + { + case fmt_type_t::DECIMAL: + stream << std::noboolalpha; + 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; + case fmt_type_t::GENERAL: + stream << std::defaultfloat; + break; + case fmt_type_t::UNSPECIFIED: + stream << std::boolalpha; + stream << std::dec; + break; + default: + break; + } + } + + void logger_t::exponential(std::ostream& stream) + { + stream << std::scientific; + } + + void logger_t::fixed(std::ostream& stream) + { + stream << std::fixed; + } + + void logger_t::compile(std::string fmt) + { + m_fmt = std::move(fmt); + m_last_fmt_pos = 0; + m_arg_pos = 0; + auto& ss = dynamic_cast(m_stream); + ss.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; + } + + std::optional> logger_t::consume_to_next_fmt() + { + auto begin = m_fmt.find('{', m_last_fmt_pos); + while (begin != std::string::npos && begin > 0 && m_fmt[begin - 1] == '\\') + begin = m_fmt.find('{', begin + 1);; + if (begin == std::string::npos) + return {}; + const auto end = find_ending_brace(begin); + if (end == std::string::npos) + { + std::stringstream ss; + ss << "Invalid format string, missing closing '}' near " << m_fmt.substr(std::min(static_cast(begin) - 5, static_cast(0))); + throw std::runtime_error(ss.str()); + } + m_last_fmt_pos = end + 1; + return std::pair{begin, end}; + } + + logger_t& get_global_logger() + { + return get_thread_context().logger; + } + + void print(std::string str) + { + const auto& config = get_global_config(); + bool should_print = true; + if (!config.get_injectors().empty()) + { + for (const auto& injector : config.get_injectors()) + { + auto [new_logging_output, should_continue, should_log] = injector->inject(str); + if (!should_log) + should_print = false; + str = std::move(new_logging_output); + if (!should_continue) + break; + } + } + if (should_print) + std::cout << str; + } + + void newline() + { + std::cout << std::endl; + } + + logging_config_t& get_global_config() + { + return global_context.global_config; + } + + void set_thread_name(const std::string& name) + { + get_thread_context().thread_name = name; + } + + const std::string& get_thread_name() + { + return get_thread_context().thread_name; + } + + std::ostream& get_local_stream() + { + auto& context = get_thread_context(); + context.logging_stream.str(""); + return context.logging_stream; + } +} diff --git a/src/blt/logging/logging_config.cpp b/src/blt/logging/logging_config.cpp new file mode 100644 index 0000000..ed29607 --- /dev/null +++ b/src/blt/logging/logging_config.cpp @@ -0,0 +1,333 @@ +/* + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +namespace blt::logging +{ + namespace tags::detail + { + hashmap_t make_map() + { + hashmap_t map{}; + map[YEAR] = log_tag_token_t::YEAR; + map[MONTH] = log_tag_token_t::MONTH; + map[DAY] = log_tag_token_t::DAY; + map[HOUR] = log_tag_token_t::HOUR; + map[MINUTE] = log_tag_token_t::MINUTE; + map[SECOND] = log_tag_token_t::SECOND; + map[MILLISECOND] = log_tag_token_t::MS; + map[NANOSECOND] = log_tag_token_t::NS; + map[UNIX_TIME] = log_tag_token_t::UNIX; + map[UNIX_TIME_NANO] = log_tag_token_t::UNIX_NANO; + map[ISO_YEAR] = log_tag_token_t::ISO_YEAR; + map[TIME] = log_tag_token_t::TIME; + map[FULL_TIME] = log_tag_token_t::FULL_TIME; + map[LOG_COLOR] = log_tag_token_t::LC; + map[ERROR_COLOR] = log_tag_token_t::EC; + map[CONDITIONAL_ERROR_COLOR] = log_tag_token_t::CEC; + map[RESET] = log_tag_token_t::RESET; + map[LOG_LEVEL] = log_tag_token_t::LL; + map[THREAD_NAME] = log_tag_token_t::TN; + map[FILE] = log_tag_token_t::FILE; + map[LINE] = log_tag_token_t::LINE; + map[STR] = log_tag_token_t::STR; + return map; + } + } + + void logging_config_t::compile() + { + static hashmap_t tag_map = tags::detail::make_map(); + m_log_tag_content.clear(); + m_log_tag_tokens.clear(); + + size_t i = 0; + for (; i < m_log_format.size(); ++i) + { + size_t start = i; + while (i < m_log_format.size() && m_log_format[i] != '{') + ++i; + if (i == m_log_format.size() || (i < m_log_format.size() && (i - start) > 0)) + { + m_log_tag_content.emplace_back(std::string_view(m_log_format.data() + start, i - start)); + m_log_tag_tokens.emplace_back(tags::detail::log_tag_token_t::CONTENT); + if (i == m_log_format.size()) + break; + } + start = i; + while (i < m_log_format.size() && m_log_format[i] != '}') + ++i; + const auto tag = std::string_view(m_log_format.data() + start, i - start + 1); + auto it = tag_map.find(tag); + if (it == tag_map.end()) + throw std::runtime_error("Invalid log tag: " + std::string(tag)); + m_log_tag_tokens.emplace_back(it->second); + } + + if (i < m_log_format.size()) + { + m_log_tag_content.emplace_back(std::string_view(m_log_format.data() + i, m_log_format.size() - i)); + m_log_tag_tokens.emplace_back(tags::detail::log_tag_token_t::CONTENT); + } + + m_longest_name_length = 0; + for (const auto& name : m_log_level_names) + m_longest_name_length = std::max(m_longest_name_length, name.size()); + } + + std::string add_year(const tm* current_time) + { + return std::to_string(current_time->tm_year + 1900); + } + + std::string add_month(const tm* current_time, const bool ensure_alignment) + { + auto str = std::to_string(current_time->tm_mon + 1); + if (ensure_alignment && str.size() < 2) + str.insert(str.begin(), '0'); + return str; + } + + std::string add_day(const tm* current_time, const bool ensure_alignment) + { + auto str = std::to_string(current_time->tm_mday); + if (ensure_alignment && str.size() < 2) + str.insert(str.begin(), '0'); + return str; + } + + std::string add_hour(const tm* current_time, const bool ensure_alignment) + { + auto str = std::to_string(current_time->tm_hour); + if (ensure_alignment && str.size() < 2) + str.insert(str.begin(), '0'); + return str; + } + + std::string add_minute(const tm* current_time, const bool ensure_alignment) + { + auto str = std::to_string(current_time->tm_min); + if (ensure_alignment && str.size() < 2) + str.insert(str.begin(), '0'); + return str; + } + + std::string add_second(const tm* current_time, const bool ensure_alignment) + { + auto str = std::to_string(current_time->tm_sec); + if (ensure_alignment && str.size() < 2) + str.insert(str.begin(), '0'); + return str; + } + + std::optional logging_config_t::generate(const std::string& user_str, const std::string& thread_name, const log_level_t level, + const char* file, const i32 line) const + { + if (level < m_level) + return {}; + + std::string fmt; + + const std::time_t time = std::time(nullptr); + const auto current_time = std::localtime(&time); + const auto millis_time = system::getCurrentTimeMilliseconds(); + const auto nano_time = system::getCurrentTimeNanoseconds(); + + size_t content = 0; + for (const auto& log_tag_token : m_log_tag_tokens) + { + switch (log_tag_token) + { + case tags::detail::log_tag_token_t::YEAR: + fmt += add_year(current_time); + break; + case tags::detail::log_tag_token_t::MONTH: + fmt += add_month(current_time, m_ensure_alignment); + break; + case tags::detail::log_tag_token_t::DAY: + fmt += add_day(current_time, m_ensure_alignment); + break; + case tags::detail::log_tag_token_t::HOUR: + fmt += add_hour(current_time, m_ensure_alignment); + break; + case tags::detail::log_tag_token_t::MINUTE: + fmt += add_minute(current_time, m_ensure_alignment); + break; + case tags::detail::log_tag_token_t::SECOND: + fmt += add_second(current_time, m_ensure_alignment); + break; + case tags::detail::log_tag_token_t::MS: + { + auto str = std::to_string(millis_time % 1000); + if (m_ensure_alignment) + { + for (size_t i = str.size(); i < 4; ++i) + str.insert(str.begin(), '0'); + } + fmt += str; + break; + } + case tags::detail::log_tag_token_t::NS: + { + auto str = std::to_string(nano_time % 1000000000ul); + if (m_ensure_alignment) + { + for (size_t i = str.size(); i < 9; ++i) + str.insert(str.begin(), '0'); + } + fmt += str; + break; + } + case tags::detail::log_tag_token_t::UNIX: + { + fmt += std::to_string(millis_time); + break; + } + case tags::detail::log_tag_token_t::UNIX_NANO: + { + fmt += std::to_string(nano_time); + break; + } + case tags::detail::log_tag_token_t::ISO_YEAR: + { + fmt += add_year(current_time); + fmt += '-'; + fmt += add_month(current_time, m_ensure_alignment); + fmt += '-'; + fmt += add_day(current_time, m_ensure_alignment); + break; + } + case tags::detail::log_tag_token_t::TIME: + fmt += add_hour(current_time, m_ensure_alignment); + fmt += ':'; + fmt += add_minute(current_time, m_ensure_alignment); + fmt += ':'; + fmt += add_second(current_time, m_ensure_alignment); + break; + case tags::detail::log_tag_token_t::FULL_TIME: + fmt += add_year(current_time); + fmt += '-'; + fmt += add_month(current_time, m_ensure_alignment); + fmt += '-'; + fmt += add_day(current_time, m_ensure_alignment); + fmt += ' '; + fmt += add_hour(current_time, m_ensure_alignment); + fmt += ':'; + fmt += add_minute(current_time, m_ensure_alignment); + fmt += ':'; + fmt += add_second(current_time, m_ensure_alignment); + break; + case tags::detail::log_tag_token_t::LC: + if (!m_use_color) + break; + fmt += m_log_level_colors[static_cast(level)]; + break; + case tags::detail::log_tag_token_t::EC: + if (!m_use_color) + break; + fmt += m_error_color; + break; + case tags::detail::log_tag_token_t::CEC: + if (!m_use_color) + break; + if (static_cast(level) >= static_cast(log_level_t::ERROR)) + fmt += m_error_color; + break; + case tags::detail::log_tag_token_t::RESET: + if (!m_use_color) + break; + fmt += build(ansi::color::color_mode::RESET_ALL); + break; + case tags::detail::log_tag_token_t::LL: + fmt += m_log_level_names[static_cast(level)]; + break; + case tags::detail::log_tag_token_t::TN: + fmt += thread_name; + break; + case tags::detail::log_tag_token_t::FILE: + if (m_print_full_name) + fmt += file; + else + fmt += fs::filename_sv(file); + break; + case tags::detail::log_tag_token_t::LINE: + fmt += std::to_string(line); + break; + case tags::detail::log_tag_token_t::STR: + fmt += user_str; + break; + case tags::detail::log_tag_token_t::CONTENT: + fmt += m_log_tag_content[content++]; + break; + } + } + + return fmt; + } + + std::string logging_config_t::get_default_log_format() + { + return build(fg(ansi::color::color8_bright::BLUE)) + "[" + tags::FULL_TIME + "]" + tags::RESET + " " + tags::LOG_COLOR + "[" + tags::LOG_LEVEL + + "]" + tags::RESET + " " + build(fg(ansi::color::color8::MAGENTA)) + "(" + tags::FILE + ":" + tags::LINE + ")" + tags::RESET + " " + + tags::CONDITIONAL_ERROR_COLOR + tags::STR + tags::RESET + "\n"; + } + + std::vector logging_config_t::get_default_log_outputs() + { + static fs::fstream_writer_t cout_writer{std::cout}; + std::vector outputs{}; + outputs.push_back(&cout_writer); + return outputs; + } + + std::array logging_config_t::get_default_log_level_colors() + { + return { + // TRACE + build(fg(ansi::color::color8_bright::WHITE)), + // DEBUG + build(fg(ansi::color::color8::CYAN)), + // INFO + build(fg(ansi::color::color8_bright::GREEN)), + // WARN + build(fg(ansi::color::color8_bright::YELLOW)), + // ERROR + build(fg(ansi::color::color8_bright::RED)), + // FATAL + build(fg(ansi::color::color8_bright::WHITE), bg(ansi::color::color8_bright::RED)), + }; + } + + std::array logging_config_t::get_default_log_level_names() + { + return {"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL",}; + } + + std::string logging_config_t::get_default_error_color() + { + return build(fg(ansi::color::color8_bright::RED)); + } +} diff --git a/src/blt/logging/status.cpp b/src/blt/logging/status.cpp new file mode 100644 index 0000000..1f3edfa --- /dev/null +++ b/src/blt/logging/status.cpp @@ -0,0 +1,225 @@ +/* + * + * 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 +#include +#ifdef unix +#include +#include +#endif +#include +#include +#include +#include +#include +#include + +namespace blt::logging +{ + vec2i get_cursor_position() + { +#ifdef unix + termios save{}, raw{}; + + tcgetattr(0, &save); + cfmakeraw(&raw); + tcsetattr(0,TCSANOW, &raw); + + char buf[32]; + char cmd[] = "\033[6n"; + + int row = 0; + int col = 0; + + if (isatty(fileno(stdin))) + { + ssize_t i = write(1, cmd, sizeof(cmd)); + (void) i; + i = read(0, buf, sizeof(buf)); + (void) i; + + int sep = 0; + int end = 0; + for (int i = 2; i < 8; i++) + { + if (buf[i] == ';') + sep = i; + if (buf[i] == 'R') + { + end = i; + break; + } + } + row = std::stoi(std::string(buf + 2, buf + sep)); + col = std::stoi(std::string(buf + sep + 1, buf + end)); + } + + tcsetattr(0,TCSANOW, &save); + + return vec2i{row, col}; +#else + return {0,0}; +#endif + } + +#define SIZE 100 + + vec2i get_screen_size() + { +#ifdef unix + char in[SIZE] = ""; + int each = 0; + int ch = 0; + int rows = 0; + int cols = 0; + termios original, changed; + + // change terminal settings + tcgetattr( STDIN_FILENO, &original); + changed = original; + changed.c_lflag &= ~( ICANON | ECHO); + changed.c_cc[VMIN] = 1; + changed.c_cc[VTIME] = 0; + tcsetattr( STDIN_FILENO, TCSANOW, &changed); + + printf ( "\033[2J"); //clear screen + + printf ( "\033[9999;9999H"); // cursor should move as far as it can + + printf ( "\033[6n"); // ask for cursor position + while ( ( ch = getchar ()) != 'R') { // R terminates the response + if ( EOF == ch) { + break; + } + if ( isprint ( ch)) { + if ( each + 1 < SIZE) { + in[each] = ch; + each++; + in[each] = '\0'; + } + } + } + + printf ( "\033[1;1H"); // move to upper left corner + if ( 2 == sscanf ( in, "[%d;%d", &rows, &cols)) { + tcsetattr( STDIN_FILENO, TCSANOW, &original); + return {rows, cols}; + } + throw std::runtime_error("Could not get screen size"); +#else + return {0,0}; +#endif + } + + i32 get_size_no_ansi(const std::string& str) + { + i32 size = 0; + for (size_t i = 0; i < str.size(); i++) + { + if (str[i] == BLT_ANSI_ESCAPE[0]) + { + while (i < str.size()) + { + if (std::isalpha(str[i++])) + break; + } + } + ++size; + } + return size - 1; + } + + std::string status_progress_bar_t::print(const vec2i, const i32 max_printed_length) const + { + std::string output = "["; + output.reserve(max_printed_length); + const auto amount_filled = (max_printed_length - 2) * m_progress; + auto amount_filled_int = static_cast(amount_filled); + const auto frac = amount_filled - static_cast(amount_filled_int); + + for (i64 i = 0; i < amount_filled_int; i++) + output += '#'; + if (frac >= 0.5) + { + output += '|'; + ++amount_filled_int; + } + for (i64 i = amount_filled_int; i < max_printed_length - 2; i++) + output += ' '; + + output += ']'; + return output; + } + + void status_progress_bar_t::set_progress(const double progress) + { + if (std::isnan(progress) || progress < 0 || progress > 1 || std::isinf(progress)) + throw std::invalid_argument("Progress must be between 0 and 1 (got: " + std::to_string(progress) + ")"); + m_progress = progress; + // m_status->redraw(); + } + + status_bar_t::status_bar_t() + { + m_screen_size = get_screen_size(); + std::cout << ansi::cursor::home << std::flush; + std::cout << ansi::erase::entire_screen << std::flush; + m_begin_position = m_last_log_position = get_cursor_position(); + std::cout << ansi::cursor::hide_cursor << std::flush; + } + + injector_output_t status_bar_t::inject(const std::string& input) + { + std::scoped_lock lock{m_print_mutex}; + injector_output_t output{input, false, false}; + if (output.new_logging_output.back() != '\n') + output.new_logging_output += '\n'; + + if (get_cursor_position() != m_begin_position) + { + for (int i = 0; i < m_status_size; i++) + std::cout << ansi::erase::entire_line << ansi::cursor::move_begin_up(1) << std::flush; + } + std::cout << ansi::erase::entire_line << std::flush; + std::cout << output.new_logging_output << std::flush; + m_max_printed_length = std::max(get_size_no_ansi(output.new_logging_output), m_max_printed_length); + m_last_log_position = get_cursor_position(); + redraw(); + + return output; + } + + void status_bar_t::compute_size() + { + m_status_size = 0; + for (const auto* ptr : m_status_items) + m_status_size += ptr->lines_used(); + } + + void status_bar_t::redraw() const + { + std::cout << ansi::cursor::move_to(m_last_log_position.x(), m_last_log_position.y()); + for (const auto* ptr : m_status_items) + std::cout << ansi::erase::entire_line << ptr->print(m_screen_size, m_max_printed_length) << std::endl; + std::cout << std::flush; + } + + status_bar_t::~status_bar_t() + { + std::cout << ansi::cursor::show_cursor << std::flush; + } +} diff --git a/src/blt/parse/argparse.cpp b/src/blt/parse/argparse.cpp index 4f8477c..19c91c6 100644 --- a/src/blt/parse/argparse.cpp +++ b/src/blt/parse/argparse.cpp @@ -196,7 +196,7 @@ namespace blt printUsage(); std::cout << getProgramName() << ": error: flag '" << flag << "' expected " << properties.a_nargs.args << " argument(s) but found '" << tokenizer.get() << "' instead!\n"; - //BLT_WARN("Expected %d arguments, found flag instead!", properties.a_nargs.args); + //BLT_WARN("Expected {:d} arguments, found flag instead!", properties.a_nargs.args); return false; } // get the value and advance diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp new file mode 100644 index 0000000..5694868 --- /dev/null +++ b/src/blt/parse/argparse_v2.cpp @@ -0,0 +1,1591 @@ +/* + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace blt::argparse +{ + constexpr static auto printer_primitive = [](const auto& v) { + std::cout << v; + }; + + constexpr static auto printer_vector = [](const auto& v) { + std::cout << "["; + for (const auto& [i, a] : enumerate(v)) + { + std::cout << a; + if (i != v.size() - 1) + std::cout << ", "; + } + std::cout << "]"; + }; + + auto print_visitor = detail::arg_meta_type_helper_t::make_visitor(printer_primitive, printer_vector); + + template + size_t get_const_char_size(const T& t) + { + if constexpr (std::is_convertible_v) + { + return std::char_traits::length(t); + } else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) + { + return 1; + } else if constexpr (std::is_same_v || std::is_same_v) + { + return t.size(); + } else + { + return 0; + } + } + + template + std::string to_string(const T& t) + { + if constexpr (std::is_same_v || std::is_same_v) + { + return std::string(t); + } else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) + { + std::string str; + str += t; + return str; + } else + { + return t; + } + } + + template + std::string make_string(Strings&&... strings) + { + std::string out; + out.reserve((get_const_char_size(strings) + ...)); + ((out += detail::ensure_is_string(std::forward(strings))), ...); + return out; + } + + template + std::vector make_arguments(Strings... strings) + { + return std::vector{"./program", strings...}; + } + + class aligned_internal_string_t + { + public: + explicit aligned_internal_string_t(std::string& str, const size_t max_line_length, const size_t line_start_size): string(str), + max_line_size( + max_line_length), + line_start_size( + line_start_size) + {} + + void add(const std::string_view str) const + { + const auto lines = string::split(string, '\n'); + if (lines.empty()) + { + string += str; + return; + } + if (lines.back().size() + str.size() > max_line_size) + { + string += '\n'; + for (size_t i = 0; i < line_start_size; i++) + string += ' '; + bool blank = true; + // we don't want to write blank only strings + for (const char c : str) + { + if (!std::isblank(c)) + { + blank = false; + break; + } + } + if (blank) + return; + } + + string += str; + } + + template + aligned_internal_string_t& operator+=(T&& value) + { + const auto str = to_string(detail::ensure_is_string(std::forward(value))); + for (size_t i = 0; i < str.size(); i++) + { + size_t j = i; + for (; j < str.size() && !std::isblank(str[j]); ++j) + {} + add(std::string_view(str.data() + i, j - i)); + if (j < str.size()) + add(std::string_view(str.data() + j, 1)); + i = j; + } + return *this; + } + + [[nodiscard]] std::string& str() const + { + return string; + } + + private: + std::string& string; + size_t max_line_size; + size_t line_start_size; + }; + + class aligner_t + { + public: + aligner_t(std::vector& buffer, const size_t start_index, const size_t max_line_size): buffer(buffer), start_index(start_index), + max_line_size(max_line_size) + {} + + void align(const size_t spaces_between) const + { + const size_t take = compute_take(); + size_t aligned_size = 0; + for (const auto& v : iterate(buffer).skip(start_index).take(take)) + { + auto size = static_cast(v.size()); + for (; size > 0 && std::isblank(v[size - 1]); size--) + {} + aligned_size = std::max(aligned_size, static_cast(size)); + } + const auto offset_size = aligned_size + spaces_between; + + for (auto& v : iterate(buffer).skip(start_index).take(take)) + { + for (size_t i = v.size(); i < offset_size; i++) + v += ' '; + } + } + + [[nodiscard]] auto iter() + { + return iterate(buffer).skip(start_index).take(compute_take()).map([this](std::string& x) { + return aligned_internal_string_t{x, max_line_size, x.size()}; + }); + } + + [[nodiscard]] auto iter() const + { + return iterate(buffer).skip(start_index).take(compute_take()).map([this](std::string& x) { + return aligned_internal_string_t{x, max_line_size, x.size()}; + }); + } + + void take(const size_t amount) + { + this->amount = amount; + } + + private: + [[nodiscard]] size_t compute_take() const + { + return amount == -1ul ? (buffer.size() - start_index - 1) : amount;; + } + + std::vector& buffer; + size_t start_index; + size_t max_line_size; + size_t amount = -1; + }; + + class aligned_printer_t + { + public: + explicit aligned_printer_t(std::string line_begin = "\t", const size_t max_line_size = 120, const size_t spaces_per_tab = 4): + line_begin(std::move(line_begin)), max_line_size(max_line_size) + { + buffer.emplace_back(); + for (size_t i = 0; i < spaces_per_tab; i++) + spaces_from_tab += ' '; + } + + [[nodiscard]] std::string str() const + { + std::string combined; + for (const auto& str : buffer) + { + combined += str; + combined += '\n'; + } + return combined; + } + + auto mark() + { + return aligner_t{buffer, buffer.size() - 1, max_line_size}; + } + + template + aligned_printer_t& add(T&& value) + { + const auto str = to_string(detail::ensure_is_string(std::forward(value))); + if (buffer.back().size() + str.size() > max_line_size) + newline(); + buffer.back() += replace_tabs(str); + return *this; + } + + void newline() + { + buffer.emplace_back(replace_tabs(line_begin)); + } + + [[nodiscard]] std::string replace_tabs(std::string str) const + { + string::replaceAll(str, "\t", spaces_from_tab); + return str; + } + + template + aligned_printer_t& operator+=(T&& value) + { + return add(std::forward(value)); + } + + [[nodiscard]] auto iter() + { + return iterate(buffer); + } + + [[nodiscard]] auto iter() const + { + return iterate(buffer); + } + + private: + std::vector buffer; + std::string line_begin; + std::string spaces_from_tab; + size_t max_line_size; + }; + + argument_builder_t& argument_builder_t::set_action(const action_t action) + { + m_action = action; + switch (m_action) + { + case action_t::STORE_TRUE: + set_nargs(0); + as_type(); + set_default(false); + break; + case action_t::STORE_FALSE: + set_nargs(0); + as_type(); + set_default(true); + break; + case action_t::STORE_CONST: + case action_t::APPEND_CONST: + set_nargs(0); + break; + case action_t::COUNT: + set_nargs(0); + as_type(); + break; + case action_t::EXTEND: + set_nargs(nargs_t::ALL); + break; + case action_t::HELP: + case action_t::VERSION: + set_nargs(0); + break; + default: + break; + } + return *this; + } + + argument_subparser_t* argument_parser_t::add_subparser(const std::string_view dest) + { + m_subparsers.emplace_back(dest, argument_subparser_t{*this}); + return &m_subparsers.back().second; + } + + argument_storage_t argument_parser_t::parse(argument_consumer_t& consumer) + { + if (!m_name) + m_name = fs::base_name_sv(consumer.absolute_first().get_argument()); + argument_positional_storage_t positional_storage{m_positional_arguments}; + hashset_t found_flags; + argument_storage_t parsed_args; + // first, we consume flags which may be part of this parser + while (consumer.can_consume() && consumer.peek().is_flag()) + handle_compound_flags(found_flags, parsed_args, consumer, consumer.consume()); + + for (auto& [key, subparser] : m_subparsers) + { + auto [parsed_subparser, storage] = subparser.parse(consumer); + storage.m_data.emplace(std::string{key}, detail::arg_data_t{std::string{parsed_subparser.get_argument()}}); + parsed_args.add(storage); + } + + while (consumer.can_consume()) + { + if (consumer.peek().is_flag()) + handle_compound_flags(found_flags, parsed_args, consumer, consumer.consume()); + else + parse_positional(parsed_args, consumer, positional_storage, consumer.peek().get_argument()); + } + handle_missing_and_default_args(m_flag_arguments, found_flags, parsed_args, "flag"); + + for (auto& [name, value] : positional_storage.remaining()) + { + std::visit(lambda_visitor{ + [](const nargs_t) {}, + [](const int argc) { + if (argc == 0) + throw detail::bad_positional("Positional Argument takes no values, this is invalid!"); + } + }, value.m_nargs); + + if (value.m_required) + throw detail::missing_argument_error(make_string("Error: argument '", name, "' was not found but is required by the program")); + if (value.m_default_value && !parsed_args.contains(value.m_dest.value_or(name))) + parsed_args.m_data.emplace(value.m_dest.value_or(name), *value.m_default_value); + } + + return parsed_args; + } + + void argument_parser_t::print_help() + { + print_usage(); + aligned_printer_t help{""}; + + if (!m_subparsers.empty()) + { + help += "Subcommands:"; + help.newline(); + for (const auto& [key, value] : m_subparsers) + { + auto map = value.get_allowed_strings(); + help += '\t'; + help += key; + help += ": {"; + for (const auto& [i, parser, strings] : enumerate(map).flatten()) + { + if (strings.size() > 1) + help += '['; + for (const auto& [i, str] : enumerate(strings)) + { + help += str; + if (i != strings.size() - 1) + help += ", "; + } + if (strings.size() > 1) + help += ']'; + if (i != map.size() - 1) + help += ", "; + } + help += "}"; + help.newline(); + } + help.newline(); + } + + if (!m_positional_arguments.empty()) + { + help += "Positional Arguments:"; + help.newline(); + auto mark = help.mark(); + for (auto& [name, builder] : m_positional_arguments) + { + help += '\t'; + if (!builder.m_required) + help += '['; + help += name; + if (!builder.m_required) + help += ']'; + help.newline(); + } + mark.align(4); + for (auto zipped_positionals : mark.iter().zip(m_positional_arguments)) + { + auto& line = std::get<0>(zipped_positionals); + auto& [name, builder] = std::get<1>(zipped_positionals); + line += builder.m_help.value_or(""); + if (builder.m_default_value && !(builder.m_action == action_t::STORE_TRUE || builder.m_action == action_t::STORE_FALSE)) + { + if (!std::isblank(line.str().back())) + line += " "; + line += "(Default: "; + std::visit(detail::arg_meta_type_helper_t::make_visitor( + [&](auto& value) + { + line += value; + }, + [&](auto& vec) + { + if constexpr (!std::is_same_v>, std::vector>) + { + line += '['; + for (const auto& [i, v] : enumerate(vec)) + { + line += v; + if (i != vec.size() - 1) + line += ", "; + } + line += ']'; + } + }), *builder.m_default_value); + line += ")"; + } + if (builder.m_choices) + { + if (!std::isblank(line.str().back())) + line += " "; + line += "(Choices: "; + for (const auto& [i, v] : enumerate(*builder.m_choices)) + { + line += '\''; + line += v; + line += '\''; + if (i != builder.m_choices->size() - 1) + line += ", "; + } + line += ')'; + } + } + } + + if (!m_flag_arguments.empty()) + { + help += "Options:"; + help.newline(); + hashmap_t> same_flags; + for (const auto& [key, value] : m_flag_arguments) + same_flags[value].emplace_back(key); + auto mark = help.mark(); + for (const auto& [builder, flag_list] : same_flags) + { + // find max size and align? + help += '\t'; + for (const auto& [i, flag] : enumerate(flag_list)) + { + help += flag; + if (i != flag_list.size() - 1) + help += ", "; + } + const argument_string_t arg{flag_list.front(), allowed_flag_prefixes}; + auto metavar = builder->m_metavar.value_or(string::toUpperCase(arg.get_name())); + auto lambda = [&]() { + help += ' '; + help += metavar; + }; + std::visit(lambda_visitor{ + [&](const nargs_t type) { + lambda(); + switch (type) + { + case nargs_t::IF_POSSIBLE: + break; + case nargs_t::ALL: + case nargs_t::ALL_AT_LEAST_ONE: + help += "..."; + break; + } + }, + [&](const int argc) { + if (argc == 0) + return; + lambda(); + if (argc > 1) + { + help += "... x"; + help += std::to_string(argc); + } + } + }, builder->m_nargs); + help.newline(); + } + mark.align(4); + for (auto zipped_flags : mark.iter().zip(same_flags)) + { + auto& str = std::get<0>(zipped_flags); + auto& [builder, flag_list] = std::get<1>(zipped_flags); + str += builder->m_help.value_or(""); + if (builder->m_default_value && !(builder->m_action == action_t::STORE_TRUE || builder->m_action == action_t::STORE_FALSE)) + { + if (!std::isblank(str.str().back())) + str += " "; + str += "(Default: '"; + std::visit(detail::arg_meta_type_helper_t::make_visitor([&](auto& value) { + str += value; + }, [&](auto& vec) { + if constexpr (!std::is_same_v>, std::vector>) + { + str += '['; + for (const auto& [i, v] : enumerate(vec)) + { + str += v; + if (i != vec.size() - 1) + str += ", "; + } + str += ']'; + } + }), *builder->m_default_value); + str += "')"; + } + if (builder->m_choices) + { + if (!std::isblank(str.str().back())) + str += " "; + str += "(Choices: "; + for (const auto& [i, v] : enumerate(*builder->m_choices)) + { + str += '\''; + str += v; + str += '\''; + if (i != builder->m_choices->size() - 1) + str += ", "; + } + str += ')'; + } + if (builder->m_required) + { + if (!std::isblank(str.str().back())) + str += " "; + str += "(Required)"; + } + } + } + + std::cout << help.str() << std::endl; + } + + void argument_parser_t::print_usage() + { + if (!m_usage) + { + aligned_printer_t aligner; + aligner += m_name.value_or(""); + aligner += ' '; + + auto parent = m_parent; + while (parent != nullptr) + { + if (!parent->m_last_parsed_parser) + throw detail::missing_value_error( + "Error: Help called on subparser but unable to find parser chain. This condition should be impossible."); + aligner += parent->m_last_parsed_parser.value(); + aligner += ' '; + parent = parent->m_parent->m_parent; + } + + for (const auto& [key, _] : m_subparsers) + { + aligner += '{'; + aligner += key; + aligner += '}'; + aligner += ' '; + } + + hashmap_t> singleFlags; + std::vector> compoundFlags; + + for (const auto& [key, value] : m_flag_arguments) + { + const argument_string_t arg{key, allowed_flag_prefixes}; + if (arg.get_flag().size() == 1) + { + if (std::holds_alternative(value->m_nargs) && std::get(value->m_nargs) == 0) + singleFlags[arg.get_flag()].emplace_back(arg.get_name()); + else + compoundFlags.emplace_back(arg, value); + } else + compoundFlags.emplace_back(arg, value); + } + + for (const auto& [i, kv] : enumerate(singleFlags)) + { + const auto& [key, value] = kv; + aligner += '['; + aligner += key; + for (const auto& name : value) + aligner += name; + aligner += ']'; + aligner += ' '; + } + + for (const auto& [i, kv] : enumerate(compoundFlags)) + { + const auto& name = kv.first; + const auto& builder = kv.second; + aligner += builder->m_required ? '<' : '['; + aligner += name.get_argument(); + auto lambda = [&]() { + aligner += ' '; + aligner += builder->m_metavar.value_or(string::toUpperCase(name.get_name())); + }; + std::visit(lambda_visitor{ + [&](const nargs_t type) { + lambda(); + switch (type) + { + case nargs_t::IF_POSSIBLE: + break; + case nargs_t::ALL: + case nargs_t::ALL_AT_LEAST_ONE: + aligner += "..."; + break; + } + }, + [&](const int argc) { + for (int j = 0; j < argc; j++) + lambda(); + } + }, builder->m_nargs); + aligner += builder->m_required ? '>' : ']'; + aligner += ' '; + } + + for (const auto& [i, pair] : enumerate(m_positional_arguments)) + { + const auto& [name, _] = pair; + aligner += '<'; + aligner += name; + aligner += '>'; + if (i != m_positional_arguments.size() - 1) + aligner += ' '; + } + + m_usage = aligner.str(); + } + std::cout << "Usage: " << *m_usage << std::endl; + } + + void argument_parser_t::print_version() const + { + std::cout << m_name.value_or("NO NAME") << " " << m_version.value_or("NO VERSION") << std::endl; + } + + void argument_parser_t::handle_compound_flags(hashset_t& found_flags, argument_storage_t& parsed_args, argument_consumer_t& consumer, + const argument_string_t& arg) + { + // i kinda hate this, TODO? + std::vector compound_flags; + if (arg.get_flag().size() == 1) + { + for (const auto c : arg.get_name()) + compound_flags.emplace_back(std::string{arg.get_flag()} + c); + } else + { + if (arg.get_flag().size() > 2) + throw detail::bad_flag(make_string("Error: Flag '", arg.get_argument(), "' is too long!")); + compound_flags.emplace_back(arg.get_argument()); + } + + for (const auto& flag_key : compound_flags) + { + const auto flag = m_flag_arguments.find(flag_key); + if (flag == m_flag_arguments.end()) + throw detail::bad_flag(make_string("Error: Unknown flag: ", flag_key)); + found_flags.insert(flag_key); + parse_flag(parsed_args, consumer, flag_key); + } + } + + void argument_parser_t::parse_flag(argument_storage_t& parsed_args, argument_consumer_t& consumer, const std::string_view arg) + { + auto flag = m_flag_arguments.find(arg)->second; + const auto dest = flag->m_dest.value_or(std::string{arg}); + std::visit(lambda_visitor{ + [&parsed_args, &consumer, &dest, &flag, arg](const nargs_t arg_enum) { + switch (arg_enum) + { + case nargs_t::IF_POSSIBLE: + if (consumer.can_consume() && !consumer.peek().is_flag()) + flag->m_dest_func(dest, parsed_args, consumer.consume().get_argument()); + else + { + if (flag->m_const_value) + parsed_args.m_data.insert({dest, *flag->m_const_value}); + } + break; + case nargs_t::ALL_AT_LEAST_ONE: + if (!consumer.can_consume()) + throw detail::missing_argument_error( + make_string("Error expected at least one argument to be consumed by '", arg, '\'')); + [[fallthrough]]; + case nargs_t::ALL: + auto result = consume_until_flag_or_end(consumer, flag->m_choices ? &*flag->m_choices : nullptr); + if (!result) + throw detail::bad_choice_error(make_string('\'', consumer.peek().get_argument(), + "' is not a valid choice for argument '", arg, + "'! Expected one of ", result.error())); + flag->m_dest_vec_func(dest, parsed_args, result.value()); + break; + } + }, + [&parsed_args, &consumer, &dest, &flag, arg, this](const i32 argc) { + const auto args = consume_argc(argc, consumer, flag->m_choices ? &*flag->m_choices : nullptr, arg); + + switch (flag->m_action) + { + case action_t::STORE: + if (argc == 0) + throw detail::missing_argument_error( + make_string("Argument '", arg, "'s action is store but takes in no arguments?")); + if (argc == 1) + flag->m_dest_func(dest, parsed_args, args.front()); + else + throw detail::unexpected_argument_error(make_string("Argument '", arg, + "'s action is store but takes in more than one argument. " + "Did you mean to use action_t::APPEND or action_t::EXTEND?")); + break; + case action_t::APPEND: + case action_t::EXTEND: + if (argc == 0) + throw detail::missing_argument_error( + make_string("Argument '", arg, "'s action is append or extend but takes in no arguments.")); + flag->m_dest_vec_func(dest, parsed_args, args); + break; + case action_t::APPEND_CONST: + if (argc != 0) + throw detail::unexpected_argument_error( + make_string("Argument '", arg, "'s action is append const but takes in arguments.")); + if (!flag->m_const_value) + { + throw detail::missing_value_error(make_string( + "Append const chosen as an action but const value not provided for argument '", arg, '\'')); + } + if (parsed_args.contains(dest)) + { + auto& data = parsed_args.m_data[dest]; + std::visit(detail::arg_meta_type_helper_t::make_visitor([arg](auto& primitive) { + throw detail::type_error(make_string("Invalid type for argument '", arg, "' expected list type, found '", + blt::type_string(), "' with value ", primitive)); + }, [&flag, arg](auto& vec) { + using type = typename meta::remove_cvref_t::value_type; + if (!std::holds_alternative(*flag->m_const_value)) + { + throw detail::type_error(make_string("Constant value for argument '", arg, + "' type doesn't match values already present! Expected to be of type '", + blt::type_string(), "'!")); + } + vec.push_back(std::get(*flag->m_const_value)); + }), data); + } else + { + std::visit(detail::arg_meta_type_helper_t::make_visitor([&parsed_args, &dest](auto& primitive) { + std::vector> vec; + vec.emplace_back(primitive); + parsed_args.m_data.emplace(dest, std::move(vec)); + }, [](auto&) { + throw detail::type_error("Append const should not be a list type!"); + }), *flag->m_const_value); + } + break; + case action_t::STORE_CONST: + if (argc != 0) + { + print_usage(); + throw detail::unexpected_argument_error( + make_string("Argument '", arg, "' is store const but called with an argument.")); + } + if (!flag->m_const_value) + throw detail::missing_value_error( + make_string("Argument '", arg, "' is store const, but const storage has no value.")); + parsed_args.m_data.emplace(dest, *flag->m_const_value); + break; + case action_t::STORE_TRUE: + if (argc != 0) + { + print_usage(); + throw detail::unexpected_argument_error("Store true flag called with an argument."); + } + parsed_args.m_data.emplace(dest, true); + break; + case action_t::STORE_FALSE: + if (argc != 0) + { + print_usage(); + throw detail::unexpected_argument_error("Store false flag called with an argument."); + } + parsed_args.m_data.insert({dest, false}); + break; + case action_t::COUNT: + if (parsed_args.m_data.contains(dest)) + { + auto visitor = detail::arg_meta_type_helper_t::make_visitor([](auto& primitive) -> detail::arg_data_t { + using type = meta::remove_cvref_t; + if constexpr (std::is_convertible_v) + { + return primitive + static_cast(1); + } else + throw detail::type_error("Error: count called but stored type is " + blt::type_string()); + }, [](auto&) -> detail::arg_data_t { + throw detail::type_error( + "List present on count. This condition doesn't make any sense! " + "(How did we get here, please report this!)"); + }); + parsed_args.m_data[dest] = std::visit(visitor, parsed_args.m_data[dest]); + } else // I also hate this! + flag->m_dest_func(dest, parsed_args, "1"); + break; + case action_t::HELP: + print_help(); + std::exit(0); + case action_t::VERSION: + print_version(); + std::exit(0); + } + } + }, flag->m_nargs); + } + + void argument_parser_t::parse_positional(argument_storage_t& parsed_args, argument_consumer_t& consumer, argument_positional_storage_t& storage, + const std::string_view arg) + { + if (!storage.has_positional()) + throw detail::missing_argument_error(make_string("Error: '", arg, "' positional argument does not match any defined for this parser")); + auto& positional = storage.next(); + const auto dest = positional.m_dest.value_or(std::string{arg}); + std::visit(lambda_visitor{ + [&consumer, &positional, &dest, &parsed_args, arg](const nargs_t arg_enum) { + switch (arg_enum) + { + case nargs_t::IF_POSSIBLE: + throw detail::bad_positional( + "Positional argument asked to consume if possible. We do not consider this to be a valid ask."); + case nargs_t::ALL_AT_LEAST_ONE: + if (!consumer.can_consume()) + throw detail::missing_argument_error( + make_string("Error expected at least one argument to be consumed by '", arg, '\'')); + [[fallthrough]]; + case nargs_t::ALL: + auto result = consume_until_flag_or_end(consumer, positional.m_choices ? &*positional.m_choices : nullptr); + if (!result) + throw detail::bad_choice_error(make_string('\'', consumer.peek().get_argument(), + "' is not a valid choice for argument '", arg, + "'! Expected one of ", result.error())); + positional.m_dest_vec_func(dest, parsed_args, result.value()); + break; + } + }, + [this, &consumer, &positional, &dest, &parsed_args, arg](const i32 argc) { + const auto args = consume_argc(argc, consumer, positional.m_choices ? &*positional.m_choices : nullptr, arg); + + switch (positional.m_action) + { + case action_t::STORE: + if (argc == 0) + throw detail::missing_argument_error( + make_string("Argument '", arg, "'s action is store but takes in no arguments?")); + if (argc == 1) + positional.m_dest_func(dest, parsed_args, args.front()); + else + throw detail::unexpected_argument_error(make_string("Argument '", arg, + "'s action is store but takes in more than one argument. " + "Did you mean to use action_t::APPEND or action_t::EXTEND?")); + break; + case action_t::APPEND: + case action_t::EXTEND: + if (argc == 0) + throw detail::missing_argument_error( + make_string("Argument '", arg, "'s action is append or extend but takes in no arguments.")); + positional.m_dest_vec_func(dest, parsed_args, args); + break; + case action_t::APPEND_CONST: + throw detail::bad_positional("action_t::APPEND_CONST does not make sense for positional arguments"); + case action_t::STORE_CONST: + throw detail::bad_positional("action_t::STORE_CONST does not make sense for positional arguments"); + case action_t::STORE_TRUE: + throw detail::bad_positional("action_t::STORE_TRUE does not make sense for positional arguments"); + case action_t::STORE_FALSE: + throw detail::bad_positional("action_t::STORE_FALSE does not make sense for positional arguments"); + case action_t::COUNT: + throw detail::bad_positional("action_t::COUNT does not make sense for positional arguments"); + case action_t::HELP: + print_help(); + std::exit(0); + case action_t::VERSION: + print_version(); + std::exit(0); + } + } + }, positional.m_nargs); + } + + void argument_parser_t::handle_missing_and_default_args(hashmap_t& arguments, + const hashset_t& found, argument_storage_t& parsed_args, + const std::string_view type) + { + for (const auto& [key, value] : arguments) + { + if (!found.contains(key)) + { + if (value->m_required) + throw detail::missing_argument_error(make_string("Error: ", type, " argument '", key, + "' was not found but is required by the program")); + auto dest = value->m_dest.value_or(std::string{key}); + if (value->m_default_value && !parsed_args.contains(dest)) + parsed_args.m_data.emplace(dest, *value->m_default_value); + } + } + } + + expected, std::string> argument_parser_t::consume_until_flag_or_end(argument_consumer_t& consumer, + hashset_t* allowed_choices) + { + std::vector args; + while (consumer.can_consume() && !consumer.peek().is_flag()) + { + if (allowed_choices != nullptr && !allowed_choices->contains(consumer.peek().get_argument())) + { + std::string valid_choices = "{"; + for (const auto& [i, choice] : enumerate(*allowed_choices)) + { + valid_choices += choice; + if (i != allowed_choices->size() - 1) + valid_choices += ", "; + } + valid_choices += "}"; + return unexpected(valid_choices); + } + args.emplace_back(consumer.consume().get_argument()); + } + return args; + } + + std::vector argument_parser_t::consume_argc(const int argc, argument_consumer_t& consumer, hashset_t* allowed_choices, + const std::string_view arg) + { + std::vector args; + for (i32 i = 0; i < argc; ++i) + { + if (!consumer.can_consume()) + { + throw detail::missing_argument_error(make_string("Expected ", argc, " arguments to be consumed by '", arg, "' but found ", i)); + } + if (consumer.peek().is_flag()) + { + std::cout << "Warning: arg '" << arg << "' expects " << argc << " arguments to be consumed but we found a flag '" << consumer.peek(). + get_argument() + << "'. We will comply as this may be desired if this argument is a file." << std::endl; + } + if (allowed_choices != nullptr && !allowed_choices->contains(consumer.peek().get_argument())) + { + std::string valid_choices = "{"; + for (const auto& [i, choice] : enumerate(*allowed_choices)) + { + valid_choices += choice; + if (i != allowed_choices->size() - 1) + valid_choices += ", "; + } + valid_choices += "}"; + throw detail::bad_choice_error(make_string('\'', consumer.peek().get_argument(), "' is not a valid choice for argument '", arg, + "'! Expected one of ", valid_choices)); + } + args.emplace_back(consumer.consume().get_argument()); + } + if (args.size() != static_cast(argc)) + { + throw std::runtime_error( + "This error condition should not be possible. " "Args consumed didn't equal the arguments requested and previous checks didn't fail. " + "Please report as an issue on the GitHub"); + } + return args; + } + + std::pair argument_subparser_t::parse(argument_consumer_t& consumer) + { + if (!consumer.can_consume()) + throw detail::missing_argument_error("Subparser requires an argument."); + const auto key = consumer.consume(); + if (key.is_flag()) + throw detail::subparse_error(key.get_argument(), to_vec(get_allowed_strings())); + const auto it = m_aliases.find(key.get_name()); + if (it == m_aliases.end()) + throw detail::subparse_error(key.get_argument(), to_vec(get_allowed_strings())); + it->second->m_name = m_parent->m_name; + m_last_parsed_parser = key.get_name(); + return {key, it->second->parse(consumer)}; + } + + hashmap_t> argument_subparser_t::get_allowed_strings() const + { + hashmap_t> map; + for (const auto& [key, value] : m_aliases) + map[value].emplace_back(key); + return map; + } + + std::vector> argument_subparser_t::to_vec(const hashmap_t>& map) + { + std::vector> vec; + for (const auto& [key, value] : map) + vec.push_back(value); + return vec; + } + + namespace detail + { + // Unit Tests for class argument_string_t + // Test Case 1: Ensure the constructor handles flags correctly + void test_argument_string_t_flag_basic(const hashset_t& prefixes) + { + const argument_string_t arg("-f", prefixes); + BLT_ASSERT(arg.is_flag() && "Expected argument to be identified as a flag."); + BLT_ASSERT(arg.value() == "f" && "Flag value should match the input string."); + } + + // Test Case 2: Ensure the constructor handles long flags correctly + void test_argument_string_t_long_flag(const hashset_t& prefixes) + { + const argument_string_t arg("--file", prefixes); + BLT_ASSERT(arg.is_flag() && "Expected argument to be identified as a flag."); + BLT_ASSERT(arg.value() == "file" && "Long flag value should match the input string."); + } + + // Test Case 3: Ensure positional arguments are correctly identified + void test_argument_string_t_positional_argument(const hashset_t& prefixes) + { + const argument_string_t arg("filename.txt", prefixes); + BLT_ASSERT(!arg.is_flag() && "Expected argument to be identified as positional."); + BLT_ASSERT(arg.value() == "filename.txt" && "Positional argument value should match the input string."); + } + + // Test Case 5: Handle an empty string + void test_argument_string_t_empty_input(const hashset_t& prefixes) + { + const argument_string_t arg("", prefixes); + BLT_ASSERT(!arg.is_flag() && "Expected an empty input to be treated as positional, not a flag."); + BLT_ASSERT(arg.value().empty() && "Empty input should have an empty value."); + } + + // Test Case 6: Handle edge case of a single hyphen (`-`) which might be ambiguous + void test_argument_string_t_single_hyphen(const hashset_t& prefixes) + { + const argument_string_t arg("-", prefixes); + BLT_ASSERT(arg.is_flag() && "Expected single hyphen (`-`) to be treated as a flag."); + BLT_ASSERT(arg.value().empty() && "Single hyphen flag should have empty value."); + BLT_ASSERT(arg.get_flag() == "-" && "Single hyphen flag should match the input string."); + } + + // Test Case 8: Handle arguments with prefix only (like "--") + void test_argument_string_t_double_hyphen(const hashset_t& prefixes) + { + const argument_string_t arg("--", prefixes); + BLT_ASSERT(arg.is_flag() && "Double hyphen ('--') should be treated as a flag."); + BLT_ASSERT(arg.value().empty() && "Double hyphen flag should have empty value."); + BLT_ASSERT(arg.get_flag() == "--" && "Double hyphen value should match the input string."); + } + + // Test Case 9: Validate edge case of an argument with spaces + void test_argument_string_t_with_spaces(const hashset_t& prefixes) + { + const argument_string_t arg(" ", prefixes); + BLT_ASSERT(!arg.is_flag() && "Arguments with spaces should not be treated as flags."); + BLT_ASSERT(arg.value() == " " && "Arguments with spaces should match the input string."); + } + + // Test Case 10: Validate arguments with numeric characters + void test_argument_string_t_numeric_flag(const hashset_t& prefixes) + { + const argument_string_t arg("-123", prefixes); + BLT_ASSERT(arg.is_flag() && "Numeric flags should still be treated as flags."); + BLT_ASSERT(arg.value() == "123" && "Numeric flag value should match the input string."); + } + + // Test Case 11: Ensure the constructor handles '+' flag correctly + void test_argument_string_t_plus_flag_basic(const hashset_t& prefixes) + { + const argument_string_t arg("+f", prefixes); + BLT_ASSERT(arg.is_flag() && "Expected argument to be identified as a flag."); + BLT_ASSERT(arg.value() == "f" && "Plus flag value should match the input string."); + } + + // Test Case 13: Handle edge case of a single plus (`+`) which might be ambiguous + void test_argument_string_t_single_plus(const hashset_t& prefixes) + { + const argument_string_t arg("+", prefixes); + BLT_ASSERT(arg.is_flag() && "Expected single plus (`+`) to be treated as a flag."); + BLT_ASSERT(arg.value().empty() && "Single plus flag should have empty value."); + BLT_ASSERT(arg.get_flag() == "+" && "Single plus flag should match the input string."); + } + + // Test Case 14: Handle arguments with prefix only (like '++') + void test_argument_string_t_double_plus(const hashset_t& prefixes) + { + const argument_string_t arg("++", prefixes); + BLT_ASSERT(arg.is_flag() && "Double plus ('++') should be treated as a flag."); + BLT_ASSERT(arg.value().empty() && "Double plus flag should have empty value."); + BLT_ASSERT(arg.get_flag() == "++" && "Double plus value should match the input string."); + } + + void run_all_tests_argument_string_t() + { + const hashset_t prefixes = {'-', '+'}; + test_argument_string_t_flag_basic(prefixes); + test_argument_string_t_long_flag(prefixes); + test_argument_string_t_positional_argument(prefixes); + test_argument_string_t_empty_input(prefixes); + test_argument_string_t_single_hyphen(prefixes); + test_argument_string_t_double_hyphen(prefixes); + test_argument_string_t_with_spaces(prefixes); + test_argument_string_t_numeric_flag(prefixes); + test_argument_string_t_plus_flag_basic(prefixes); + test_argument_string_t_single_plus(prefixes); + test_argument_string_t_double_plus(prefixes); + } + + void test_argparse_empty() + { + const std::vector argv{"./program"}; + argument_parser_t parser; + const auto args = parser.parse(argv); + BLT_ASSERT(args.size() == 0 && "Empty argparse should have no args on output"); + } + + void test_single_flag_prefixes() + { + argument_parser_t parser; + parser.add_flag("-a").set_action(action_t::STORE_TRUE); + parser.add_flag("+b").set_action(action_t::STORE_FALSE); + parser.add_flag("/c").as_type().set_action(action_t::STORE); + + const std::vector args = {"./program", "-a", "+b", "/c", "42"}; + const auto parsed_args = parser.parse(args); + + BLT_ASSERT(parsed_args.get("-a") == true && "Flag '-a' should store `true`"); + BLT_ASSERT(parsed_args.get("+b") == false && "Flag '+b' should store `false`"); + BLT_ASSERT(parsed_args.get("/c") == 42 && "Flag '/c' should store the value 42"); + } + + // Test: Invalid flag prefixes + void test_invalid_flag_prefixes() + { + argument_parser_t parser; + parser.add_flag("-a"); + parser.add_flag("+b"); + parser.add_flag("/c"); + + const std::vector args = {"./program", "!d", "-a"}; + try + { + parser.parse(args); + BLT_ASSERT(false && "Parsing should fail with invalid flag prefix '!'"); + } catch (...) + { + BLT_ASSERT(true && "Correctly threw on bad flag prefix"); + } + } + + void test_compound_flags() + { + argument_parser_t parser; + parser.add_flag("-v").as_type().set_action(action_t::COUNT); + + const std::vector args = {"./program", "-vvv"}; + const auto parsed_args = parser.parse(args); + + BLT_ASSERT(parsed_args.get("-v") == 3 && "Flag '-v' should count occurrences in compound form"); + } + + void test_combination_of_valid_and_invalid_flags() + { + using namespace argparse; + + argument_parser_t parser; + parser.add_flag("-x").as_type(); + parser.add_flag("/y").as_type(); + + const std::vector args = {"./program", "-x", "10", "!z", "/y", "value"}; + try + { + parser.parse(args); + BLT_ASSERT(false && "Parsing should fail due to invalid flag '!z'"); + } catch (...) + { + BLT_ASSERT(true && "Correctly threw an exception for invalid flag"); + } + } + + void test_flags_with_different_actions() + { + using namespace argparse; + + argument_parser_t parser; + parser.add_flag("-k").as_type().set_action(action_t::STORE); // STORE action + parser.add_flag("-t").as_type().set_action(action_t::STORE_CONST).set_const(999); // STORE_CONST action + parser.add_flag("-f").set_action(action_t::STORE_FALSE); // STORE_FALSE action + parser.add_flag("-c").set_action(action_t::STORE_TRUE); // STORE_TRUE action + + const std::vector args = {"./program", "-k", "100", "-t", "-f", "-c"}; + const auto parsed_args = parser.parse(args); + + BLT_ASSERT(parsed_args.get("-k") == 100 && "Flag '-k' should store 100"); + BLT_ASSERT(parsed_args.get("-t") == 999 && "Flag '-t' should store a const value of 999"); + BLT_ASSERT(parsed_args.get("-f") == false && "Flag '-f' should store `false`"); + BLT_ASSERT(parsed_args.get("-c") == true && "Flag '-c' should store `true`"); + } + + // Helper function to simulate argument parsing for nargs tests + bool parse_arguments(const std::vector& args, const nargs_v expected_nargs) + { + argument_parser_t parser; + + std::vector arg_strings; + arg_strings.reserve(args.size()); + for (const auto& arg : args) + arg_strings.emplace_back(arg, parser.get_allowed_flag_prefixes()); + argument_consumer_t consumer{arg_strings}; + + parser.add_positional("positional").set_nargs(expected_nargs); + try + { + auto parsed_args = parser.parse(consumer); + return consumer.remaining() == 0; + } catch (const std::exception&) + { + return false; + } + } + + // Test case for nargs = 0 + void test_nargs_0() + { + std::cout << "[Running Test: test_nargs_0]\n"; + + // Valid case: No arguments + const std::vector valid_args = {"./program"}; + BLT_ASSERT(!parse_arguments(valid_args, 0) && "nargs=0: Should fail"); + + // Invalid case: 1 argument + const std::vector invalid_args = {"./program", "arg1"}; + BLT_ASSERT(!parse_arguments(invalid_args, 0) && "nargs=0: Should not accept any arguments"); + + std::cout << "Success: test_nargs_0\n"; + } + + // Test case for nargs = 1 + void test_nargs_1() + { + std::cout << "[Running Test: test_nargs_1]\n"; + + // Valid case: 1 argument + const std::vector valid_args = {"./program", "arg1"}; + BLT_ASSERT(parse_arguments(valid_args, 1) && "nargs=1: Should accept exactly 1 argument"); + + // Invalid case: 0 arguments + const std::vector invalid_args_0 = {"./program"}; + BLT_ASSERT(!parse_arguments(invalid_args_0, 1) && "nargs=1: Should not accept 0 arguments"); + + // Invalid case: 2 arguments + const std::vector invalid_args_2 = {"./program", "arg1", "arg2"}; + BLT_ASSERT(!parse_arguments(invalid_args_2, 1) && "nargs=1: Should not accept more than 1 argument"); + + std::cout << "Success: test_nargs_1\n"; + } + + // Test case for nargs = 2 + void test_nargs_2() + { + std::cout << "[Running Test: test_nargs_2]\n"; + + // Valid case: 2 arguments + const std::vector valid_args = {"./program", "arg1", "arg2"}; + BLT_ASSERT(!parse_arguments(valid_args, 2) && "nargs=2: Should fail as action is store"); + + // Invalid case: 0 arguments + const std::vector invalid_args_0 = {"./program"}; + BLT_ASSERT(!parse_arguments(invalid_args_0, 2) && "nargs=2: Should not accept 0 arguments"); + + // Invalid case: 1 argument + const std::vector invalid_args_1 = {"./program", "arg1"}; + BLT_ASSERT(!parse_arguments(invalid_args_1, 2) && "nargs=2: Should not accept less than 2 arguments"); + + // Invalid case: 3 arguments + const std::vector invalid_args_3 = {"./program", "arg1", "arg2", "arg3"}; + BLT_ASSERT(!parse_arguments(invalid_args_3, 2) && "nargs=2: Should not accept more than 2 arguments"); + + std::cout << "Success: test_nargs_2\n"; + } + + void test_nargs_all() + { + std::cout << "[Running Test: test_nargs_all]\n"; + + // Valid case: No arguments + const std::vector valid_args_0 = {"./program"}; + BLT_ASSERT(!parse_arguments(valid_args_0, argparse::nargs_t::ALL) && "nargs=ALL: No arguments present. Should fail.)"); + + // Valid case: Multiple arguments + const std::vector valid_args_2 = {"./program", "arg1", "arg2"}; + BLT_ASSERT(parse_arguments(valid_args_2, argparse::nargs_t::ALL) && "nargs=ALL: Should accept all remaining arguments"); + + // Valid case: Many arguments + const std::vector valid_args_many = {"./program", "arg1", "arg2", "arg3", "arg4"}; + BLT_ASSERT(parse_arguments(valid_args_many, argparse::nargs_t::ALL) && "nargs=ALL: Should accept all remaining arguments"); + + std::cout << "Success: test_nargs_all\n"; + } + + // Test case for nargs_t::ALL_AT_LEAST_ONE + void test_nargs_all_at_least_one() + { + std::cout << "[Running Test: test_nargs_all_at_least_one]\n"; + + // Valid case: 1 argument + const std::vector valid_args_1 = {"./program", "arg1"}; + BLT_ASSERT(parse_arguments(valid_args_1, argparse::nargs_t::ALL_AT_LEAST_ONE) && + "nargs=ALL_AT_LEAST_ONE: Should accept at least one argument and consume it"); + + // Valid case: Multiple arguments + const std::vector valid_args_3 = {"./program", "arg1", "arg2", "arg3"}; + BLT_ASSERT(parse_arguments(valid_args_3, argparse::nargs_t::ALL_AT_LEAST_ONE) && + "nargs=ALL_AT_LEAST_ONE: Should accept at least one argument and consume all remaining arguments"); + + // Invalid case: No arguments + const std::vector invalid_args_0 = {"./program"}; + BLT_ASSERT(!parse_arguments(invalid_args_0, argparse::nargs_t::ALL_AT_LEAST_ONE) && + "nargs=ALL_AT_LEAST_ONE: Should reject if no arguments are provided"); + + std::cout << "Success: test_nargs_all_at_least_one\n"; + } + + void run_combined_flag_test() + { + std::cout << "[Running Test: run_combined_flag_test]\n"; + argument_parser_t parser; + + parser.add_flag("-a").set_action(action_t::STORE_TRUE); + parser.add_flag("--deep").set_action(action_t::STORE_FALSE); + parser.add_flag("-b", "--combined").set_action(action_t::STORE_CONST).set_const(50); + parser.add_flag("--append").set_action(action_t::APPEND).as_type(); + parser.add_flag("--required").set_required(true); + parser.add_flag("--default").set_default("I am a default value"); + parser.add_flag("-t").set_action(action_t::APPEND_CONST).set_dest("test").set_const(5); + parser.add_flag("-g").set_action(action_t::APPEND_CONST).set_dest("test").set_const(10); + parser.add_flag("-e").set_action(action_t::APPEND_CONST).set_dest("test").set_const(15); + parser.add_flag("-f").set_action(action_t::APPEND_CONST).set_dest("test").set_const(20); + parser.add_flag("-d").set_action(action_t::APPEND_CONST).set_dest("test").set_const(25); + parser.add_flag("--end").set_action(action_t::EXTEND).set_dest("wow").as_type(); + + const auto a1 = make_arguments("-a", "--required", "hello"); + const auto r1 = parser.parse(a1); + BLT_ASSERT(r1.get("-a") == true && "Flag '-a' should store true"); + BLT_ASSERT(r1.get("--default") == "I am a default value" && "Flag '--default' should store default value"); + BLT_ASSERT(r1.get("--required") == "hello" && "Flag '--required' should store 'hello'"); + + const auto a2 = make_arguments("-a", "--deep", "--required", "soft"); + const auto r2 = parser.parse(a2); + BLT_ASSERT(r2.get("-a") == true && "Flag '-a' should store true"); + BLT_ASSERT(r2.get("--deep") == false && "Flag '--deep' should store false"); + BLT_ASSERT(r2.get("--required") == "soft" && "Flag '--required' should store 'soft'"); + + const auto a3 = make_arguments("--required", "silly", "--combined", "-t", "-f", "-e"); + const auto r3 = parser.parse(a3); + BLT_ASSERT((r3.get>("test") == std::vector{5, 20, 15}) && "Flags should add to vector of {5, 20, 15}"); + BLT_ASSERT(r3.get("-b") == 50 && "Combined flag should store const of 50"); + + const auto a4 = make_arguments("--required", "crazy", "--end", "10", "12.05", "68.11", "100.00", "200532", "-d", "-t", "-g", "-e", "-f"); + const auto r4 = parser.parse(a4); + BLT_ASSERT( + (r4.get>("test") == std::vector{25, 5, 10, 15, 20}) && + "Expected test vector to be filled with all arguments in order of flags"); + BLT_ASSERT( + (r4.get>("wow") == std::vector{10, 12.05, 68.11, 100.00, 200532}) && + "Extend vector expected to contain all elements"); + + std::cout << "Success: run_combined_flag_test\n"; + } + + void run_choice_test() + { + std::cout << "[Running Test: run_choice_test]\n"; + argument_parser_t parser; + + parser.add_flag("--hello").set_choices("silly", "crazy", "soft"); + parser.add_positional("iam").set_choices("different", "choices", "for", "me"); + + const auto a1 = make_arguments("--hello", "crazy", "different"); + const auto r1 = parser.parse(a1); + BLT_ASSERT(r1.get("--hello") == "crazy" && "Flag '--hello' should store 'crazy'"); + BLT_ASSERT(r1.get("iam") == "different" && "Positional 'iam' should store 'different'"); + + const auto a2 = make_arguments("--hello", "not_an_option", "different"); + try + { + parser.parse(a2); + BLT_ASSERT(false && "Parsing should fail due to invalid flag '--hello'"); + } catch (...) + {} + + const auto a3 = make_arguments("--hello", "crazy", "not_a_choice"); + try + { + parser.parse(a3); + BLT_ASSERT(false && "Parsing should fail due to invalid positional 'iam'"); + } catch (...) + {} + + std::cout << "Success: run_choice_test\n"; + } + + void run_subparser_test() + { + std::cout << "[Running Test: run_subparser_test]\n"; + argument_parser_t parser; + + parser.add_flag("--open").make_flag(); + + const auto subparser = parser.add_subparser("mode"); + + const auto n1 = subparser->add_parser("n1"); + n1->add_flag("--silly").make_flag(); + n1->add_positional("path"); + + const auto n2 = subparser->add_parser("n2"); + n2->add_flag("--crazy").make_flag(); + n2->add_positional("path"); + n2->add_positional("output"); + + const auto n3 = subparser->add_parser("n3"); + n3->add_flag("--deep").make_flag(); + + const auto a1 = make_arguments("n1", "--silly"); + try + { + parser.parse(a1); + BLT_ASSERT(false && "Subparser should throw an error when positional not supplied"); + } catch (...) + {} + + const auto a2 = make_arguments("--open"); + try + { + parser.parse(a2); + BLT_ASSERT(false && "Subparser should throw an error when no subparser is supplied"); + } catch (...) + {} + + const auto a3 = make_arguments("n1", "--silly", "path_n1"); + const auto r3 = parser.parse(a3); + BLT_ASSERT(r3.get("--open") == false && "Flag '--open' should default to false"); + BLT_ASSERT(r3.get("mode") == "n1" && "Subparser should store 'n1'"); + BLT_ASSERT(r3.get("path") == "path_n1" && "Subparser path should be 'path'"); + + const auto a4 = make_arguments("n2", "--crazy", "path"); + + try + { + parser.parse(a4); + BLT_ASSERT(false && "Subparser should throw an error when second positional is not supplied"); + } catch (...) + {} + + const auto a5 = make_arguments("--open", "n2", "path_n2", "output_n2"); + const auto r5 = parser.parse(a5); + BLT_ASSERT(r5.get("--open") == true && "Flag '--open' should store true"); + BLT_ASSERT(r5.get("mode") == "n2" && "Subparser should store 'n2'"); + BLT_ASSERT(r5.get("path") == "path_n2" && "Subparser path should be 'path'"); + BLT_ASSERT(r5.get("output") == "output_n2" && "Subparser output should be 'output'"); + + const auto a6 = make_arguments("not_an_option", "silly"); + + try + { + parser.parse(a6); + BLT_ASSERT(false && "Subparser should throw an error when first positional is not a valid subparser"); + } catch (const std::exception&) + {} + + const auto a7 = make_arguments("n3"); + const auto r7 = parser.parse(a7); + BLT_ASSERT(r7.get("mode") == "n3" && "Subparser should store 'n3'"); + + std::cout << "Success: run_subparser_test\n"; + } + + void run_argparse_flag_tests() + { + test_single_flag_prefixes(); + test_invalid_flag_prefixes(); + test_compound_flags(); + test_combination_of_valid_and_invalid_flags(); + test_flags_with_different_actions(); + run_combined_flag_test(); + run_choice_test(); + run_subparser_test(); + } + + void run_all_nargs_tests() + { + test_nargs_0(); + test_nargs_1(); + test_nargs_2(); + test_nargs_all(); + test_nargs_all_at_least_one(); + std::cout << "All nargs tests passed successfully.\n"; + } + + void test() + { + run_all_tests_argument_string_t(); + test_argparse_empty(); + run_argparse_flag_tests(); + run_all_nargs_tests(); + } + + [[nodiscard]] std::string subparse_error::error_string() const + { + std::string message = "Subparser Error: "; + message += m_found_string; + message += " is not a valid command. Allowed commands are: {"; + for (const auto [i, allowed_string] : enumerate(m_allowed_strings)) + { + if (allowed_string.size() > 1) + message += '['; + for (const auto [j, alias] : enumerate(allowed_string)) + { + message += alias; + if (static_cast(j) < static_cast(allowed_string.size()) - 2) + message += ", "; + else if (static_cast(j) < static_cast(allowed_string.size()) - 1) + message += ", or "; + } + if (allowed_string.size() > 1) + message += ']'; + if (i != m_allowed_strings.size() - 1) + message += ", "; + } + message += "}"; + return message; + } + } +} diff --git a/src/blt/parse/obj_loader.cpp b/src/blt/parse/obj_loader.cpp index 82a3c2d..1449d60 100644 --- a/src/blt/parse/obj_loader.cpp +++ b/src/blt/parse/obj_loader.cpp @@ -26,231 +26,231 @@ #include #include "blt/std/assert.h" #include "blt/std/utility.h" -#include +#include namespace blt::parse { - class char_tokenizer - { - private: - std::string_view string; - std::size_t current_pos = 0; - public: - explicit char_tokenizer(std::string_view view): string(view) - {} - - inline char advance() - { - return string[current_pos++]; - } - - inline bool has_next(size_t offset = 0) - { - return current_pos + offset < string.size(); - } - - inline std::string_view read_fully() - { - return blt::string::trim(string.substr(current_pos)); - } - }; - - template - T get(std::string_view str) - { - T x; - // TODO: GCC version. C++17 supports from_chars but GCC8.5 doesn't have floating point. -#if __cplusplus >= BLT_CPP20 + class char_tokenizer + { + private: + std::string_view string; + std::size_t current_pos = 0; + + public: + explicit char_tokenizer(std::string_view view): string(view) + {} + + inline char advance() + { + return string[current_pos++]; + } + + inline bool has_next(size_t offset = 0) + { + return current_pos + offset < string.size(); + } + + inline std::string_view read_fully() + { + return blt::string::trim(string.substr(current_pos)); + } + }; + + template + T get(std::string_view str) + { + T x; + // TODO: GCC version. C++17 supports from_chars but GCC8.5 doesn't have floating point. + #if __cplusplus >= BLT_CPP20 const auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), x); -#else - auto ec = std::errc(); - if constexpr (std::is_floating_point_v) - { - x = static_cast(std::stod(std::string(str))); - } else if constexpr (std::is_integral_v) - { - x = static_cast(std::stoll(std::string(str))); - } else - static_assert( - "You are using a c++ version which does not support the required std::from_chars, manual conversion has failed to find a type!"); -#endif - // probably not needed. - if (ec != std::errc()) - { -// int i; -// const auto [ptr2, ec2] = std::from_chars(str.data(), str.data() + str.size(), i); -// if (ec2 == std::errc()) -// { -// x = static_cast(i); -// } else -// { - BLT_WARN("Unable to parse string '%s' into number!", std::string(str).c_str()); - x = 0; -// } - } - return x; - } - - void obj_loader::parse_vertex_line(char_tokenizer& tokenizer) - { - char type = tokenizer.advance(); - - if (type == 'p') - { - BLT_WARN("Unexpected type '%c' (not supported)", type); - return; - } - - auto elements = blt::string::split(std::string(tokenizer.read_fully()), " "); - BLT_ASSERT(elements.size() >= 2 && "Current line doesn't have enough arguments to process!"); - float x = get(elements[0]), y = get(elements[1]); - BLT_DEBUG_STREAM << "Loaded value of (" << x << ", " << y << ")"; - if (elements.size() < 3) - { - if (type == 't') - uvs.push_back(uv_t{x, y}); - else - BLT_ERROR("Unable to parse line '%s' type '%c' not recognized for arg count", std::string(tokenizer.read_fully()).c_str(), type); - } else - { - float z = get(elements[2]); - BLT_DEBUG_STREAM << " with z: " << z; - if (!handle_vertex_and_normals(x, y, z, type)) - BLT_ERROR("Unable to parse line '%s' type '%c' not recognized", std::string(tokenizer.read_fully()).c_str(), type); - } - BLT_DEBUG_STREAM << "\n"; - } - - bool obj_loader::handle_vertex_and_normals(float x, float y, float z, char type) - { - if (std::isspace(type)) - { - vertices.push_back(vertex_t{x, y, z}); - } else if (type == 'n') - { - normals.push_back(normal_t{x, y, z}); - } else - return false; - return true; - } - - obj_model_t quick_load(std::string_view file) - { - return obj_loader().parseFile(file); - } - - obj_model_t obj_loader::parseFile(std::string_view file) - { - auto lines = blt::fs::getLinesFromFile(std::string(file)); - for (const auto& [index, line] : blt::enumerate(lines)) - { - current_line = index; - char_tokenizer token(line); - if (!token.has_next() || token.read_fully().empty()) - continue; - switch (token.advance()) - { - case '#': - continue; - case 'f': - parse_face(token); - break; - case 'v': - parse_vertex_line(token); - break; - case 'o': - { - current_object.object_names.emplace_back(token.read_fully()); - BLT_TRACE("Setting object '%s'", std::string(current_object.object_name).c_str()); - break; - } - case 'm': - { - while (token.has_next() && token.advance() != ' ') - {} - BLT_WARN("Material '%s' needs to be loaded!", std::string(token.read_fully()).c_str()); - break; - } - case 'u': - { - if (!current_object.indices.empty()) - data.push_back(current_object); - current_object = {}; - while (token.has_next() && token.advance() != ' ') - {} - current_object.material = token.read_fully(); - //BLT_WARN("Using material '%s'", std::string(token.read_fully()).c_str()); - break; - } - case 's': - //BLT_WARN("Using shading: %s", std::string(token.read_fully()).c_str()); - break; - } - } - data.push_back(current_object); - return {std::move(vertex_data), std::move(data), std::move(materials)}; - } - - void obj_loader::parse_face(char_tokenizer& tokenizer) - { - auto faces = blt::string::split(std::string(tokenizer.read_fully()), ' '); - if (faces.size() == 3) - { - triangle_t triangle{}; - handle_face_vertex(faces, triangle.v); - current_object.indices.push_back(triangle); - } else if (faces.size() == 4) - { - quad_t quad{}; - handle_face_vertex(faces, quad.v); - triangle_t t1{}; - triangle_t t2{}; - - for (int i = 0; i < 3; i++) - t1.v[i] = quad.v[i]; - t2.v[0] = quad.v[0]; - t2.v[1] = quad.v[2]; - t2.v[2] = quad.v[3]; - - current_object.indices.push_back(t1); - current_object.indices.push_back(t2); - } else - BLT_WARN("Unsupported face vertex count of %d on line %d!", faces.size(), current_line); - } - - void obj_loader::handle_face_vertex(const std::vector& face_list, int32_t* arr) - { - for (const auto& [e_index, value] : blt::enumerate(face_list)) - { - auto indices = blt::string::split(value, '/'); - BLT_ASSERT(indices.size() == 3 && "Must have vertex, uv, and normal indices!!"); - - auto vi = get(indices[0]) - 1; - auto ui = get(indices[1]) - 1; - auto ni = get(indices[2]) - 1; - - BLT_DEBUG("Found vertex: %d, UV: %d, and normal: %d", vi, ui, ni); - - face_t face{vi, ui, ni}; - - auto loc = vertex_map.find(face); - if (loc == vertex_map.end()) - { - BLT_DEBUG("DID NOT FIND FACE!"); - auto index = static_cast(vertex_data.size()); - vertex_data.push_back({vertices[vi], uvs[ui], normals[ni]}); - BLT_DEBUG("Vertex: (%f, %f, %f), UV: (%f, %f), Normal: (%f, %f, %f)", vertices[vi].x(), vertices[vi].y(), vertices[vi].z(), - uvs[ui].x(), uvs[ui].y(), normals[ni].x(), normals[ni].y(), normals[ni].z()); - vertex_map.insert({face, index}); - arr[e_index] = index; - } else - { - BLT_TRACE("Using cached data; %d; map size: %d", loc->second, vertex_data.size()); - //const auto& d = vertex_data[loc->second]; - BLT_TRACE("Vertex: (%f, %f, %f), UV: (%f, %f), Normal: (%f, %f, %f)", d.vertex.x(), d.vertex.y(), d.vertex.z(), - d.uv.x(), d.uv.y(), d.normal.x(), d.normal.y(), d.normal.z()); - arr[e_index] = loc->second; - } - } - } -} \ No newline at end of file + #else + auto ec = std::errc(); + if constexpr (std::is_floating_point_v) + { + x = static_cast(std::stod(std::string(str))); + } else if constexpr (std::is_integral_v) + { + x = static_cast(std::stoll(std::string(str))); + } else + static_assert( + "You are using a c++ version which does not support the required std::from_chars, manual conversion has failed to find a type!"); + #endif + // probably not needed. + if (ec != std::errc()) + { + // int i; + // const auto [ptr2, ec2] = std::from_chars(str.data(), str.data() + str.size(), i); + // if (ec2 == std::errc()) + // { + // x = static_cast(i); + // } else + // { + BLT_WARN("Unable to parse string '{}' into number!", std::string(str).c_str()); + x = 0; + // } + } + return x; + } + + void obj_loader::parse_vertex_line(char_tokenizer& tokenizer) + { + char type = tokenizer.advance(); + + if (type == 'p') + { + BLT_WARN("Unexpected type '{:c}' (not supported)", type); + return; + } + + auto elements = string::split(tokenizer.read_fully(), " "); + BLT_ASSERT(elements.size() >= 2 && "Current line doesn't have enough arguments to process!"); + float x = get(elements[0]), y = get(elements[1]); + BLT_DEBUG("Loaded value of ({}, {})", x, y); + if (elements.size() < 3) + { + if (type == 't') + uvs.push_back(uv_t{x, y}); + else + BLT_ERROR("Unable to parse line '{}' type '{:c}' not recognized for arg count", tokenizer.read_fully(), type); + } else + { + float z = get(elements[2]); + BLT_DEBUG(" with z: {}", z); + if (!handle_vertex_and_normals(x, y, z, type)) + BLT_ERROR("Unable to parse line '{}' type '{:c}' not recognized", tokenizer.read_fully(), type); + } + } + + bool obj_loader::handle_vertex_and_normals(float x, float y, float z, char type) + { + if (std::isspace(type)) + { + vertices.push_back(vertex_t{x, y, z}); + } else if (type == 'n') + { + normals.push_back(normal_t{x, y, z}); + } else + return false; + return true; + } + + obj_model_t quick_load(std::string_view file) + { + return obj_loader().parseFile(file); + } + + obj_model_t obj_loader::parseFile(std::string_view file) + { + auto lines = blt::fs::getLinesFromFile(std::string(file)); + for (const auto& [index, line] : blt::enumerate(lines)) + { + current_line = index; + char_tokenizer token(line); + if (!token.has_next() || token.read_fully().empty()) + continue; + switch (token.advance()) + { + case '#': + continue; + case 'f': + parse_face(token); + break; + case 'v': + parse_vertex_line(token); + break; + case 'o': + { + current_object.object_names.emplace_back(token.read_fully()); + BLT_TRACE("Setting object '{}'", std::string(current_object.object_name).c_str()); + break; + } + case 'm': + { + while (token.has_next() && token.advance() != ' ') + {} + BLT_WARN("Material '{}' needs to be loaded!", std::string(token.read_fully()).c_str()); + break; + } + case 'u': + { + if (!current_object.indices.empty()) + data.push_back(current_object); + current_object = {}; + while (token.has_next() && token.advance() != ' ') + {} + current_object.material = token.read_fully(); + //BLT_WARN("Using material '{}'", std::string(token.read_fully()).c_str()); + break; + } + case 's': + //BLT_WARN("Using shading: {}", std::string(token.read_fully()).c_str()); + break; + } + } + data.push_back(current_object); + return {std::move(vertex_data), std::move(data), std::move(materials)}; + } + + void obj_loader::parse_face(char_tokenizer& tokenizer) + { + auto faces = blt::string::split(std::string(tokenizer.read_fully()), ' '); + if (faces.size() == 3) + { + triangle_t triangle{}; + handle_face_vertex(faces, triangle.v); + current_object.indices.push_back(triangle); + } else if (faces.size() == 4) + { + quad_t quad{}; + handle_face_vertex(faces, quad.v); + triangle_t t1{}; + triangle_t t2{}; + + for (int i = 0; i < 3; i++) + t1.v[i] = quad.v[i]; + t2.v[0] = quad.v[0]; + t2.v[1] = quad.v[2]; + t2.v[2] = quad.v[3]; + + current_object.indices.push_back(t1); + current_object.indices.push_back(t2); + } else + BLT_WARN("Unsupported face vertex count of {:d} on line {:d}!", faces.size(), current_line); + } + + void obj_loader::handle_face_vertex(const std::vector& face_list, int32_t* arr) + { + for (const auto& [e_index, value] : blt::enumerate(face_list)) + { + auto indices = blt::string::split(value, '/'); + BLT_ASSERT(indices.size() == 3 && "Must have vertex, uv, and normal indices!!"); + + auto vi = get(indices[0]) - 1; + auto ui = get(indices[1]) - 1; + auto ni = get(indices[2]) - 1; + + BLT_DEBUG("Found vertex: {:d}, UV: {:d}, and normal: {:d}", vi, ui, ni); + + face_t face{vi, ui, ni}; + + auto loc = vertex_map.find(face); + if (loc == vertex_map.end()) + { + BLT_DEBUG("DID NOT FIND FACE!"); + auto index = static_cast(vertex_data.size()); + vertex_data.push_back({vertices[vi], uvs[ui], normals[ni]}); + BLT_DEBUG("Vertex: ({.4f}, {.4f}, {.4f}), UV: ({.4f}, {.4f}), Normal: ({.4f}, {.4f}, {:.4f})", vertices[vi].x(), vertices[vi].y(), + vertices[vi].z(), uvs[ui].x(), uvs[ui].y(), normals[ni].x(), normals[ni].y(), normals[ni].z()); + vertex_map.insert({face, index}); + arr[e_index] = index; + } else + { + BLT_TRACE("Using cached data; {:d}; map size: {:d}", loc->second, vertex_data.size()); + //const auto& d = vertex_data[loc->second]; + BLT_TRACE("Vertex: ({.4f}, {.4f}, {.4f}), UV: ({.4f}, {.4f}), Normal: ({.4f}, {.4f}, {:.4f})", d.vertex.x(), d.vertex.y(), + d.vertex.z(), d.uv.x(), d.uv.y(), d.normal.x(), d.normal.y(), d.normal.z()); + arr[e_index] = loc->second; + } + } + } +} diff --git a/src/blt/parse/templating.cpp b/src/blt/parse/templating.cpp index b56bef9..f2221c1 100644 --- a/src/blt/parse/templating.cpp +++ b/src/blt/parse/templating.cpp @@ -18,7 +18,7 @@ #include #include #include -#include "blt/std/logging.h" +#include "blt/logging/logging.h" namespace blt { @@ -275,7 +275,7 @@ namespace blt values.push_back(b1 ^ b2); break; default: - BLT_WARN("Unexpected token '%s'", std::string(next.token).c_str()); + BLT_WARN("Unexpected token '{}'", std::string(next.token).c_str()); return blt::unexpected(template_parser_failure_t::BOOL_TYPE_NOT_FOUND); } } diff --git a/src/blt/profiling/profiler.cpp b/src/blt/profiling/profiler.cpp index b088016..68e5cdb 100644 --- a/src/blt/profiling/profiler.cpp +++ b/src/blt/profiling/profiler.cpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include #include @@ -32,9 +32,9 @@ namespace blt::profiling { difference(difference), name(std::move(name)), total(total) {} }; - inline void println(const std::vector&& lines, logging::log_level level) { + inline void println(const std::vector&& lines, const logging::log_level_t level) { for (const auto& line : lines) - BLT_LOG_STREAM(level) << line << "\n"; + BLT_LOG(level, "{}", line); // auto& logger = logging::getLoggerFromLevel(level); // for (const auto& line : lines) // logger << line << "\n"; @@ -108,7 +108,7 @@ namespace blt::profiling { } void printProfile( - const std::string& profileName, logging::log_level loggingLevel, bool averageHistory + const std::string& profileName, const logging::log_level_t loggingLevel, const bool averageHistory ) { auto& profile = profiles[profileName]; const auto& intervals = profile.intervals; diff --git a/src/blt/profiling/profiler_v2.cpp b/src/blt/profiling/profiler_v2.cpp index 00dd5f7..75e12ee 100644 --- a/src/blt/profiling/profiler_v2.cpp +++ b/src/blt/profiling/profiler_v2.cpp @@ -187,11 +187,11 @@ namespace blt stream << line << "\n"; } - void printProfile(profile_t& profiler, const std::uint32_t flags, sort_by sort, blt::logging::log_level log_level) + void printProfile(profile_t& profiler, const std::uint32_t flags, sort_by sort, blt::logging::log_level_t log_level) { std::stringstream stream; writeProfile(stream, profiler, flags, sort); - BLT_LOG_STREAM(log_level) << stream.str(); + BLT_LOG(log_level, "{}", stream.str()); } profile_t::~profile_t() @@ -237,7 +237,7 @@ namespace blt profiles.erase(profile_name); } - void _internal::printProfile(const std::string& profile_name, std::uint32_t flags, sort_by sort, blt::logging::log_level log_level) + void _internal::printProfile(const std::string& profile_name, std::uint32_t flags, sort_by sort, blt::logging::log_level_t log_level) { if (profiles.find(profile_name) == profiles.end()) return; diff --git a/src/blt/std/assert.cpp b/src/blt/std/assert.cpp index 5ed4440..68db4df 100644 --- a/src/blt/std/assert.cpp +++ b/src/blt/std/assert.cpp @@ -3,15 +3,15 @@ * Licensed under GNU General Public License V3.0 * See LICENSE file for license detail */ -#include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include struct abort_exception final : public std::exception { @@ -48,8 +48,8 @@ struct abort_exception final : public std::exception #ifdef IS_GNU_BACKTRACE - #include #include +#include #endif @@ -86,7 +86,7 @@ namespace blt #ifdef IS_GNU_BACKTRACE BLT_STACK_TRACE(50); - BLT_ERROR("An exception '%s' has occurred in file '%s:%d'", what, path, line); + BLT_ERROR("An exception '{}' has occurred in file '{}:{:d}'", what, path, line); BLT_ERROR("Stack Trace:"); printStacktrace(messages, size, path, line); @@ -103,7 +103,7 @@ namespace blt #ifdef IS_GNU_BACKTRACE BLT_STACK_TRACE(50); - BLT_ERROR("The assertion '%s' has failed in file '%s:%d'", expression, path, line); + BLT_ERROR("The assertion '{}' has failed in file '{}:{:d}'", expression, path, line); if (msg != nullptr) BLT_ERROR(msg); BLT_ERROR("Stack Trace:"); @@ -175,8 +175,8 @@ namespace blt BLT_STACK_TRACE(50); #endif BLT_FATAL("----{BLT ABORT}----"); - BLT_FATAL("\tWhat: %s", what); - BLT_FATAL("\tCalled from %s:%d", path, line); + BLT_FATAL("\tWhat: {}", what); + BLT_FATAL("\tCalled from {}:{:d}", path, line); #ifdef IS_GNU_BACKTRACE printStacktrace(messages, size, path, line); diff --git a/src/blt/std/error.cpp b/src/blt/std/error.cpp index c733e0e..bc658c7 100644 --- a/src/blt/std/error.cpp +++ b/src/blt/std/error.cpp @@ -15,68 +15,52 @@ * along with this program. If not, see . */ #include -#include +#include namespace blt::error { - void print_socket_error() - { - switch (errno) - { - case EINVAL: - BLT_WARN("Invalid argument"); - break; - case EACCES: - BLT_WARN("Permission denied"); - break; - case EPERM: - BLT_WARN("Operation not permitted"); - break; - case EADDRINUSE: - BLT_WARN("Address already in use"); - break; - case EADDRNOTAVAIL: - BLT_WARN("Cannot copy_fast requested address"); - break; - case EAFNOSUPPORT: - BLT_WARN("Address family not supported by protocol"); - break; - case EAGAIN: - BLT_WARN("Try again"); - break; - case EALREADY: - BLT_WARN("Operation already in progress"); - break; - case EBADF: - BLT_WARN("Bad file number"); - break; - case ECONNREFUSED: - BLT_WARN("Connection refused"); - break; - case EFAULT: - BLT_WARN("Bad address"); - break; - case EINPROGRESS: - BLT_WARN("Operation now in progress"); - break; - case EINTR: - BLT_WARN("Interrupted system call"); - break; - case EISCONN: - BLT_WARN("Transport endpoint is already connected"); - break; - case ENETUNREACH: - BLT_WARN("Network is unreachable"); - break; - case ENOTSOCK: - BLT_WARN("Socket operation_t on non-socket"); - break; - case EPROTOTYPE: - BLT_WARN("Protocol wrong type for socket"); - break; - case ETIMEDOUT: - BLT_WARN("Connection timed out"); - break; - } - } -} \ No newline at end of file + void print_socket_error() + { + switch (errno) + { + case EINVAL: BLT_WARN("Invalid argument"); + break; + case EACCES: BLT_WARN("Permission denied"); + break; + case EPERM: BLT_WARN("Operation not permitted"); + break; + case EADDRINUSE: BLT_WARN("Address already in use"); + break; + case EADDRNOTAVAIL: BLT_WARN("Cannot copy_fast requested address"); + break; + case EAFNOSUPPORT: BLT_WARN("Address family not supported by protocol"); + break; + case EAGAIN: BLT_WARN("Try again"); + break; + case EALREADY: BLT_WARN("Operation already in progress"); + break; + case EBADF: BLT_WARN("Bad file number"); + break; + case ECONNREFUSED: BLT_WARN("Connection refused"); + break; + case EFAULT: BLT_WARN("Bad address"); + break; + case EINPROGRESS: BLT_WARN("Operation now in progress"); + break; + case EINTR: BLT_WARN("Interrupted system call"); + break; + case EISCONN: BLT_WARN("Transport endpoint is already connected"); + break; + case ENETUNREACH: BLT_WARN("Network is unreachable"); + break; + case ENOTSOCK: BLT_WARN("Socket operation_t on non-socket"); + break; + case EPROTOTYPE: BLT_WARN("Protocol wrong type for socket"); + break; + case ETIMEDOUT: BLT_WARN("Connection timed out"); + break; + default: + break; + } + } +} diff --git a/src/blt/std/logging.cpp b/src/blt/std/logging.cpp deleted file mode 100644 index 129d1f2..0000000 --- a/src/blt/std/logging.cpp +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Created by Brett on 20/07/23. - * Licensed under GNU General Public License V3.0 - * See LICENSE file for license detail - */ -#define BLT_LOGGING_IMPLEMENTATION -#include diff --git a/src/blt/std/mmap.cpp b/src/blt/std/mmap.cpp index ef7a978..c65d9b7 100644 --- a/src/blt/std/mmap.cpp +++ b/src/blt/std/mmap.cpp @@ -15,6 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#include #include #ifdef __unix__ @@ -112,7 +113,7 @@ namespace blt #ifdef __unix__ if (munmap(ptr, bytes)) { - BLT_ERROR_STREAM << "Failed to deallocate\n"; + BLT_ERROR("Failed to deallocate"); throw bad_alloc_t(handle_mmap_error()); } #else diff --git a/src/blt/std/simd.cpp b/src/blt/std/simd.cpp index 1c89acb..85b4e8c 100644 --- a/src/blt/std/simd.cpp +++ b/src/blt/std/simd.cpp @@ -16,7 +16,6 @@ * along with this program. If not, see . */ #include -#include namespace blt { diff --git a/src/blt/std/system.cpp b/src/blt/std/system.cpp index 546bac6..9147178 100644 --- a/src/blt/std/system.cpp +++ b/src/blt/std/system.cpp @@ -4,7 +4,7 @@ * See LICENSE file for license detail */ #include -#include +#include #if !defined(_MSC_VER) && !defined(WIN32) #include /* for struct timeval */ @@ -94,7 +94,7 @@ namespace blt if (GetProcessTimes(GetCurrentProcess(), &starttime, &exittime, &kerneltime, &usertime) == 0) { - BLT_WARN("Unable to get process resource usage, error: %d", GetLastError()); + BLT_WARN("Unable to get process resource usage, error: {:d}", GetLastError()); return {}; } @@ -111,7 +111,7 @@ namespace blt #else if (getrusage(who, (struct rusage*) &usage) != 0) { - BLT_ERROR("Failed to get rusage %d", errno); + BLT_ERROR("Failed to get rusage {:d}", errno); return {}; } #endif diff --git a/test.txt b/test.txt new file mode 100644 index 0000000..8416185 --- /dev/null +++ b/test.txt @@ -0,0 +1,3 @@ +This is a println with a stream +This is a mixed print 25 with multiple types 34.233400 +What about just a new line character? diff --git a/tests/argparse_tests.cpp b/tests/argparse_tests.cpp new file mode 100644 index 0000000..124bfc6 --- /dev/null +++ b/tests/argparse_tests.cpp @@ -0,0 +1,23 @@ +/* + * + * 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 + +int main(){ + blt::argparse::detail::test(); + return 0; +} \ No newline at end of file diff --git a/tests/iterator_tests.cpp b/tests/iterator_tests.cpp index 66f787f..347141f 100644 --- a/tests/iterator_tests.cpp +++ b/tests/iterator_tests.cpp @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#include +#include #include #include #include @@ -64,18 +64,18 @@ void test_enumerate() { blt::log_box_t box(std::cout, "Enumerate Tests", 25); for (const auto& [index, item] : blt::enumerate(array_1)) - BLT_TRACE_STREAM << index << " : " << item << "\n"; + BLT_TRACE("{}, : {}", index, item); BLT_TRACE(""); for (const auto& [index, item] : blt::enumerate(array_1).rev()) - BLT_TRACE_STREAM << index << " : " << item << "\n"; + BLT_TRACE("{}, : {}", index, item); BLT_TRACE(""); for (const auto& [index, item] : blt::enumerate(array_1).take(3)) { - BLT_TRACE_STREAM << index << " : " << item << "\n"; + BLT_TRACE("{}, : {}", index, item); BLT_ASSERT(index < 3); } @@ -83,7 +83,7 @@ void test_enumerate() for (const auto& [index, item] : blt::enumerate(array_1).take(3).rev()) { - BLT_TRACE_STREAM << index << " : " << item << "\n"; + BLT_TRACE("{}, : {}", index, item); BLT_ASSERT(index < 3); } @@ -91,7 +91,7 @@ void test_enumerate() for (const auto& [index, item] : blt::enumerate(array_1).skip(3)) { - BLT_TRACE_STREAM << index << " : " << item << "\n"; + BLT_TRACE("{}, : {}", index, item); BLT_ASSERT(index >= 3); } @@ -99,7 +99,7 @@ void test_enumerate() for (const auto& [index, item] : blt::enumerate(array_1).skip(3).rev()) { - BLT_TRACE_STREAM << index << " : " << item << "\n"; + BLT_TRACE("{}, : {}", index, item); BLT_ASSERT(index >= 3); } @@ -107,7 +107,7 @@ void test_enumerate() for (const auto& [index, item] : blt::enumerate(array_1).skip(3).take(5)) { - BLT_TRACE_STREAM << index << " : " << item << "\n"; + BLT_TRACE("{}, : {}", index, item); BLT_ASSERT(index >= 3 && index < (array_1.size() - 5) + 3); } @@ -115,7 +115,7 @@ void test_enumerate() for (const auto& [index, item] : blt::enumerate(array_1).skip(3).rev().take(5)) { - BLT_TRACE_STREAM << index << " : " << item << "\n"; + BLT_TRACE("{}, : {}", index, item); BLT_ASSERT(index >= 5); } } @@ -125,7 +125,7 @@ void test_pairs() blt::log_box_t box(std::cout, "Pairs Tests", 25); for (auto [a1, a2] : blt::in_pairs(array_1, array_2)) { - BLT_TRACE_STREAM << a1 << " : " << a2 << "\n"; + BLT_TRACE("{}, : {}", a1, a2); } } @@ -134,32 +134,32 @@ void test_zip() blt::log_box_t box(std::cout, "Zip Tests", 25); for (auto [a1, a2, a3] : blt::zip(array_1, array_2, list_1)) { - BLT_TRACE_STREAM << a1 << " : " << a2 << " : " << a3 << "\n"; + BLT_TRACE("{:.4} : {:.4} : {:.4}", a1, a2, a3); } BLT_TRACE("================================"); for (auto [a1, a2, a3] : blt::zip(array_1, array_2, list_1).take(3)) { - BLT_TRACE_STREAM << a1 << " : " << a2 << " : " << a3 << "\n"; + BLT_TRACE("{:.4} : {:.4} : {:.4}", a1, a2, a3); } BLT_TRACE("================================"); for (auto [a1, a2, a3] : blt::zip(array_1, array_2, array_3).take(3).rev()) { - BLT_TRACE_STREAM << a1 << " : " << a2 << " : " << a3 << "\n"; + BLT_TRACE("{:.4} : {:.4} : {:.4}", a1, a2, a3); } BLT_TRACE("================================"); for (auto [a1, a2, a3] : blt::zip(array_1, array_2, array_3).take_or(13)) { - BLT_TRACE_STREAM << a1 << " : " << a2 << " : " << a3 << "\n"; + BLT_TRACE("{:.4} : {:.4} : {:.4}", a1, a2, a3); } BLT_TRACE("================================"); for (auto [a1, a2, a3] : blt::zip(array_1, array_2, array_3).rev().take(3)) { - BLT_TRACE_STREAM << a1 << " : " << a2 << " : " << a3 << "\n"; + BLT_TRACE("{:.4} : {:.4} : {:.4}", a1, a2, a3); } BLT_TRACE("================================"); for (auto [a1, a2, a3] : blt::zip(array_1, array_2, array_3).skip(2).rev()) { - BLT_TRACE_STREAM << a1 << " : " << a2 << " : " << a3 << "\n"; + BLT_TRACE("{:.4} : {:.4} : {:.4}", a1, a2, a3); } } @@ -168,33 +168,33 @@ void test_iterate() blt::log_box_t box(std::cout, "Iterate Tests", 25); for (auto v : blt::iterate(array_1)) { - BLT_TRACE_STREAM << "Element: " << v << "\n"; + BLT_TRACE("Element: {:.4f}", v); } BLT_TRACE("================================"); for (auto v : blt::iterate(array_1).skip(5)) { - BLT_TRACE_STREAM << "Element: " << v << "\n"; + BLT_TRACE("Element: {:.4f}", v); } BLT_TRACE("================================"); for (auto v : blt::iterate(array_1).take(5)) { - BLT_TRACE_STREAM << "Element: " << v << "\n"; + BLT_TRACE("Element: {:.4f}", v); } BLT_TRACE("================================"); for (auto v : blt::iterate(array_1).rev()) { - BLT_TRACE_STREAM << "Element: " << v << "\n"; + BLT_TRACE("Element: {:.4f}", v); } BLT_TRACE("================================"); for (auto [a, b] : blt::iterate(array_1).zip(list_1)) { - BLT_TRACE_STREAM << "Zip: " << a << " " << b << "\n"; + BLT_TRACE("Zip: {:.4f} {:.4f}", a, b); } BLT_TRACE("================================"); for (auto [i, data] : blt::iterate(array_1).map([](const blt::vec2& in) { return in.normalize(); }).zip(list_1).skip(3).take(4).enumerate()) { auto [a, b] = data; - BLT_TRACE_STREAM << "Map + Zip + Skip + Take + Enumerate (Index: " << i << ")> " << a << " " << b << "\n"; + BLT_TRACE("Map + Zip + Skip + Take + Enumerate (Index: {})> {:.4f} {:.4f}", i, a, b); } BLT_TRACE("================================"); for (auto [i, data] : blt::iterate(array_1).map( @@ -203,7 +203,7 @@ void test_iterate() }).zip(list_1).skip(3).take(4).enumerate()) { auto [a, b] = data; - BLT_TRACE_STREAM << "Map + Zip + Skip + Take + Enumerate (Index: " << i << ")> " << a << " " << b << "\n"; + BLT_TRACE("Map + Zip + Skip + Take + Enumerate (Index: {})> {:.4f} {:.4f}", i, a, b); } BLT_TRACE("================================"); for (auto a : blt::iterate(array_1).map([](const blt::vec2& in) { return in.normalize(); }) @@ -212,16 +212,16 @@ void test_iterate() if (!a) continue; auto v = *a; - BLT_TRACE_STREAM << " So this one works? " << v << "\n"; + BLT_TRACE(" So this one works? {:.4f}", v); } BLT_TRACE("================================"); for (auto a : blt::iterate(array_1).map([](const blt::vec2& in) { return in.normalize(); }) - .enumerate().filter([](const auto& f) { return f.value.x() > 0.5; })) + .enumerate().filter([](const auto& f) { return f.second.x() > 0.5; })) { if (!a) continue; auto [index, v] = *a; - BLT_TRACE_STREAM << " So this one works? (" << index << ")" << v << "\n"; + BLT_TRACE(" So this one works? ({}) {:.4f}", index, v); } BLT_TRACE("================================"); // for (auto a : blt::iterate(array_1).filter([](const auto& f) { return f.x() > 3 && f.y() < 6; }).take(2)) @@ -233,7 +233,7 @@ void test_iterate() // } for (auto a : blt::iterate(array_1).map([](const auto& f) { return f.x() > 3 && f.y() < 6; })) { - BLT_TRACE_STREAM << " How about this one?? " << a << "\n"; + BLT_TRACE(" How about this one?? ({}) {:.4f}", a); } // for (auto [value, a] : blt::iterate(array_1).map( diff --git a/tests/logger_tests.cpp b/tests/logger_tests.cpp new file mode 100644 index 0000000..c00d01d --- /dev/null +++ b/tests/logger_tests.cpp @@ -0,0 +1,229 @@ +/* + * + * 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 +#include +#include +#include +#include +#include +#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 +This is a println with alignment right (fill with reserved character) ^^^^^^^^46 end value +This is a println with fill no alignment %%%%%%%%%%%%%%%%%%46 end value +This is a println with arg reference 46.02 +This is a println with arg reference &&&&&&&&&&&&&&&&&&&& +)"); + +std::pair compare_strings(const std::string& s1, const std::string& s2) +{ + const auto size = std::min(s1.size(), s2.size()); + size_t index = 0; + for (; index < 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(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()}; + } + } + if (s1.size() != s2.size()) + return {false, "Strings size do not match '" + std::to_string(s1.size()) + "' vs '" + std::to_string(s2.size()) + "'"}; + return {true, ""}; +} + +int main() +{ + 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::println(ss, "This is a println with alignment right (fill with reserved character) {:\\^>10} end value", 46); + blt::logging::println(ss, "This is a println with fill no alignment {:%20} end value", 46); + blt::logging::println(ss, "This is a println with arg reference {0:{1}.{2}f}", 46.0232, 20, 2); + blt::logging::println(ss, "This is a println with arg reference {0:&{1}}", "", 20); + // 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()); + + namespace ansi = blt::logging::ansi; + namespace color = ansi::color; + + // for (blt::u8 r = 0; r < 6; r++) + // { + // for (blt::u8 g = 0; g < 6; g++) + // { + // for (blt::u8 b = 0; b < 6; b++) + // { + // blt::logging::println("{}This is a println with a color {:#3x} {:#3x} {:#3x}{}", + // build(fg(color::color256{r, g, b}), bg(color::color256{ + // static_cast(5 - r), + // static_cast(5 - g), + // static_cast(5 - b) + // })), r, g, b, build(color::color_mode::RESET_ALL)); + // } + // } + // } + // blt::logging::println("{}This is a color now with background{}", + // build(color::color_mode::BOLD, fg(color::color8::RED), color::color_mode::DIM, bg(color::color_rgb(0, 100, 255))), + // build(color::color_mode::RESET_ALL)); + + + std::ofstream os("test.txt"); + blt::fs::fstream_writer_t wtr(os); + blt::fs::writer_string_wrapper_t writer(wtr); + + writer.write("This is a println with a stream\n"); + writer.write("This is a mixed print "); + writer.write(std::to_string(25)); + writer.write(" with multiple types "); + writer.write(std::to_string(34.23340)); + writer.write('\n'); + writer.write("What about just a new line character?\n"); + + // blt::logging::println("Logged {} characters", charCount); + // + // BLT_TRACE("Hello this is am empty trace!"); + // BLT_TRACE("This is a trace with data {} {} {}", "bad at code", 413, "boy"); + // + // BLT_DEBUG("This is complete? {}", "this is working!"); + // BLT_INFO("Hello there!"); + // BLT_WARN("This is a warning!"); + // BLT_ERROR("This is an error!"); + // BLT_FATAL("This is a fatal error!"); + // BLT_TRACE("This is a pointer {:f}", &charCount); + // + // BLT_TRACE("Now time to test the logger status box"); + + blt::logging::status_progress_bar_t progress; + blt::logging::status_bar_t status; + status.add(progress); + blt::logging::get_global_config().add_injector(status); + + progress.set_progress(1.0 / 103.0); + BLT_TRACE("Hello There!"); + std::this_thread::sleep_for(std::chrono::milliseconds(1500)); + progress.set_progress(2.0 / 103.0); + BLT_TRACE("I am printing stuff!"); + std::this_thread::sleep_for(std::chrono::milliseconds(1500)); + progress.set_progress(3.0 / 103.0); + BLT_TRACE("How are you!?"); + + for (int i = 0; i < 100; i++) + { + progress.set_progress((4.0 + i) / 103.0); + BLT_INFO("I am printing some output {} times!", i + 1); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + std::this_thread::sleep_for(std::chrono::milliseconds(5000)); + + /*std::cout << "\033[2J"; + constexpr int totalRows = 24; + // std::cout << "\033[1;" << (totalRows - 1) << "r"; + std::cout << use_mode(ansi::mode::color80x25_text); + + for (int i = 1; i <= 10; ++i) + { + + std::cout << "\033[1;1H"; + std::cout << "printed line " << i << std::endl; + std::cout << "\033[" << totalRows << ";1H"; + std::cout << "[----status----]" << std::flush; + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } + + std::cout << "\033[r";*/ + + // blt::logging::println("This is println {}\twith a std::endl in the middle of it"); +}