diff --git a/source/utility/.gitignore b/source/utility/.gitignore new file mode 100644 index 00000000..65e31120 --- /dev/null +++ b/source/utility/.gitignore @@ -0,0 +1,34 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +.vs \ No newline at end of file diff --git a/source/utility/LICENSE b/source/utility/LICENSE new file mode 100644 index 00000000..43773010 --- /dev/null +++ b/source/utility/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 goubermouche + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/source/utility/README.md b/source/utility/README.md new file mode 100644 index 00000000..a97f75cc --- /dev/null +++ b/source/utility/README.md @@ -0,0 +1 @@ +# util \ No newline at end of file diff --git a/source/utility/containers/allocator_based_containers/linked_list.h b/source/utility/containers/allocator_based_containers/linked_list.h deleted file mode 100644 index 80989d18..00000000 --- a/source/utility/containers/allocator_based_containers/linked_list.h +++ /dev/null @@ -1,87 +0,0 @@ -#pragma once -#include "utility/containers/handle.h" - -namespace utility { - template - class linked_list { - struct node { - type value; - handle next; - }; - public: - class iterator { - handle current; - - public: - iterator(handle ptr) : current(ptr) {} - - type& operator*() { - return current->value; - } - - iterator& operator++() { - current = current->next; - return *this; - } - - bool operator!=(const iterator& other) const { - return current != other.current; - } - }; - - template - void append(const type& value, allocator& alloc) { - handle new_node = allocate_node(alloc); - new_node->value = value; - new_node->next = nullptr; - - if (m_first == nullptr) { - m_first = m_last = new_node; - } - else { - m_last->next = new_node; - m_last = new_node; - } - - m_size++; - } - - type& get_first() { - return m_first->value; - } - - type& get_last() { - return m_last->value; - } - - const type& get_first() const { - return m_first->value; - } - - u64 get_size() const { - return m_size; - } - - bool empty() const { - return m_size == 0; - } - - iterator begin() { - return iterator(m_first); - } - - iterator end() { - return iterator(nullptr); - } - protected: - template - static handle allocate_node(allocator& alloc) { - return static_cast(alloc.allocate(sizeof(node))); - } - protected: - handle m_first = nullptr; - handle m_last = nullptr; - - u64 m_size = 0; - }; -} diff --git a/source/utility/containers/allocators/block_allocator.cpp b/source/utility/containers/allocators/block_allocator.cpp deleted file mode 100644 index 37b50045..00000000 --- a/source/utility/containers/allocators/block_allocator.cpp +++ /dev/null @@ -1,106 +0,0 @@ -#include "block_allocator.h" -#include "utility/macros.h" - -namespace utility { - block_allocator::block::block(u8* memory) : memory(memory), position(0) {} - - block_allocator::block::~block() { - std::free(memory); - } - - block_allocator::block_allocator(u64 block_size) : m_block_size(block_size) { - allocate_block(); // allocate the first block - m_first_block = m_current_block; - } - - block_allocator::~block_allocator() { - while (m_first_block) { - const block* temp = m_first_block; - m_first_block = m_first_block->next; - delete temp; - } - } - - void block_allocator::print_bytes() const { - const u8 digit_count = num_digits(get_block_count()) + 3; - const block* temp = m_first_block; - u64 index = 0; - - while (temp) { - console::print("{:<{}}", std::format("{}:", index++), digit_count); - - for (u64 i = 0; i < temp->position; i++) { - console::print("{:02X} ", temp->memory[i]); - } - - console::print("\n"); - temp = temp->next; - } - } - - void block_allocator::print_used() const { - const u8 digit_count = num_digits(get_block_count()) + 3; - const double percent = static_cast(m_block_size) / 100.0; - const block* temp = m_first_block; - - u64 wasted_bytes = 0; - u64 index = 0; - - while (temp) { - double used_percent = static_cast(temp->position) / percent; - wasted_bytes += m_block_size - temp->position; - temp = temp->next; - - console::print("{:<{}}{:.2f}%\n", std::format("{}:", index++), digit_count, used_percent); - } - - const u64 total_bytes = get_block_count() * m_block_size; - const double total_percent = static_cast(total_bytes) / 100.0; - const double wasted_percent = static_cast(wasted_bytes) / total_percent; - - console::print("\nwasted {}B / {}B ({:.2f}%)\n", wasted_bytes, total_bytes, wasted_percent); - } - - auto block_allocator::allocate(u64 size) -> void* { - ASSERT(size <= m_block_size, "insufficient block size for allocation of {}B", size); - - if(size == 0) { - return nullptr; - } - - // if this allocation incurs a buffer overflow allocate a new block - if (m_current_block->position + size >= m_block_size) { - allocate_block(); - } - - void* memory = m_current_block->memory + m_current_block->position; - m_current_block->position += size; - return memory; - } - - auto block_allocator::allocate_zero(u64 size) -> void* { - void* memory = allocate(size); - std::memset(memory, 0, size); - return memory; - } - - auto block_allocator::get_block_count() const -> u64 { - return m_block_count; - } - - auto block_allocator::get_block_size() const -> u64 { - return m_block_size; - } - - void block_allocator::allocate_block() { - const auto memory = static_cast(std::malloc(m_block_size)); - const auto new_block = new block(memory); - - if (m_current_block) { - m_current_block->next = new_block; - } - - m_current_block = new_block; - m_block_count++; - } -} diff --git a/source/utility/containers/dense_set.cpp b/source/utility/containers/dense_set.cpp deleted file mode 100644 index 5797974b..00000000 --- a/source/utility/containers/dense_set.cpp +++ /dev/null @@ -1,78 +0,0 @@ -#include "dense_set.h" - -namespace utility { - dense_set::dense_set(u64 capacity) : m_capacity(capacity) { - m_data.resize((capacity + 63) / 64, 0); - std::ranges::fill(m_data.begin(), m_data.end(), 0); - } - - void dense_set::clear() { - std::fill(m_data.begin(), m_data.end(), 0); - } - - bool dense_set::set_union(const dense_set& src) { - ASSERT(m_capacity >= src.m_capacity, "panic"); - u64 n = (src.m_capacity + 63) / 64; - u64 changes = 0; - - for (u64 i = 0; i < n; ++i) { - u64 old = m_data[i]; - u64 new_val = old | src.m_data[i]; - - m_data[i] = new_val; - changes |= (old ^ new_val); - } - - return static_cast(changes); - } - - void dense_set::copy(const dense_set& src) { - ASSERT(m_capacity >= src.m_capacity, "panic"); - std::ranges::copy(src.m_data.begin(), src.m_data.end(), m_data.begin()); - } - - void dense_set::put(u64 index) - { - u64 slot = index / 64; - u64 pos = index % 64; - - if (slot >= m_data.size()) { - m_data.resize((index * 2 + 63) / 64, 0); - m_capacity = index * 2; - } - - m_data[slot] |= (1ull << pos); - } - - void dense_set::remove(u64 index) { - u64 slot = index / 64; - u64 pos = index % 64; - - if (slot < m_data.size()) { - m_data[slot] &= ~(1ull << pos); - } - } - - bool dense_set::get(u64 index) const { - u64 slot = index / 64; - u64 pos = index % 64; - - if (slot >= m_data.size()) { - return false; - } - - return m_data[slot] & (1ull << pos); - } - - u64 dense_set::data(u64 index) const { - return m_data[index]; - } - - u64& dense_set::data(u64 index) { - return m_data[index]; - } - - u64 dense_set::capacity() const { - return m_capacity; - } -} \ No newline at end of file diff --git a/source/utility/containers/dense_set.h b/source/utility/containers/dense_set.h deleted file mode 100644 index 5338e118..00000000 --- a/source/utility/containers/dense_set.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once -#include "utility/macros.h" - -namespace utility { - // TODO: add documentation - // TODO: explore alternate datastructures which may result - // in better performance/cleaner code - - class dense_set { - public: - dense_set() = default; - dense_set(u64 capacity); - - void clear(); - - bool set_union(const dense_set& src); - void copy(const dense_set& src); - - void put(u64 index); - void remove(u64 index); - bool get(u64 index) const; - - u64 data(u64 index) const; - u64& data(u64 index); - - u64 capacity() const; - private: - std::vector m_data; - u64 m_capacity; - }; - - template - void foreach_set(const dense_set& ds, function func) { - for (u64 i = 0; i < (ds.capacity() + 63) / 64; ++i) { - u64 bits = ds.data(i); - - for (u64 it = i * 64; bits; bits >>= 1, ++it) { - if (bits & 1) { - func(it); - } - } - } - } -} \ No newline at end of file diff --git a/source/utility/containers/handle.h b/source/utility/containers/handle.h deleted file mode 100644 index 5c90bfa0..00000000 --- a/source/utility/containers/handle.h +++ /dev/null @@ -1,70 +0,0 @@ -#pragma once -#include "utility/types.h" -#include - -namespace utility::types { - // NOTE: due to the lightweight nature of this class, atomic operations are NOT used - // and thus the container is not thread-safe. - - /** - * \brief Simple and lightweight non-owning pointer abstraction. - * \tparam type Type of the contained pointer - */ - template - class handle { - public: - handle() = default; - handle(type* ptr) : m_ptr(ptr) {} - - template - handle(other_type* other) : m_ptr(reinterpret_cast(other)) {} - - template - handle(handle other) : m_ptr(reinterpret_cast(other.get())) {} - - auto operator*() const -> type& { - return *m_ptr; - } - - auto operator->() const -> type* { - return m_ptr; - } - - [[nodiscard]] auto get() const -> type* { - return m_ptr; - } - - auto operator==(const handle& other) const -> bool { - return m_ptr == other.m_ptr; - } - - operator bool() const noexcept { - return m_ptr != nullptr; - } - protected: - type* m_ptr = nullptr; - }; -} // namespace utility - -template -struct std::hash> { - utility::u64 operator()(const utility::types::handle& h) const noexcept { - // hash the internal pointer - return std::hash{}(h.get()); - } -}; - -template -struct std::formatter> { - auto parse(format_parse_context& ctx) { - return ctx.begin(); - } - - auto format(const utility::types::handle& obj, format_context& ctx) { - if (obj) { - return format_to(ctx.out(), "{}", static_cast(obj.get())); - } - - return format_to(ctx.out(), "0"); - } -}; diff --git a/source/utility/containers/iterators/const_iterator.h b/source/utility/containers/iterators/const_iterator.h deleted file mode 100644 index 4ec2d7bd..00000000 --- a/source/utility/containers/iterators/const_iterator.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -namespace utility { - template - class const_iterator { - public: - const_iterator(const type* m_ptr) : m_ptr(m_ptr) {} - - const_iterator& operator++() { - ++m_ptr; - return *this; - } - - const_iterator operator++(int) { - const_iterator temp = *this; - ++m_ptr; - return temp; - } - - const type& operator*() const { - return *m_ptr; - } - - bool operator==(const const_iterator& other) const { - return m_ptr == other.m_ptr; - } - - bool operator!=(const const_iterator& other) const { - return m_ptr != other.m_ptr; - } - private: - const type* m_ptr; - }; -} diff --git a/source/utility/containers/iterators/iterator.h b/source/utility/containers/iterators/iterator.h deleted file mode 100644 index 5ba9435f..00000000 --- a/source/utility/containers/iterators/iterator.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -namespace utility { - template - class iterator { - public: - iterator(type* m_ptr) : m_ptr(m_ptr) {} - - iterator& operator++() { - ++m_ptr; - return *this; - } - - iterator operator++(int) { - iterator temp = *this; - ++m_ptr; - return temp; - } - - type& operator*() { - return *m_ptr; - } - - bool operator==(const iterator& other) const { - return m_ptr == other.m_ptr; - } - - bool operator!=(const iterator& other) const { - return m_ptr != other.m_ptr; - } - private: - type* m_ptr; - }; -} diff --git a/source/utility/containers/stack.h b/source/utility/containers/stack.h deleted file mode 100644 index 0a08c6df..00000000 --- a/source/utility/containers/stack.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once -#include "utility/containers/contiguous_container.h" - -namespace utility { - template - class stack : public contiguous_container { - public: - stack() - : contiguous_container() {} - - stack(const std::initializer_list& initializer_list) - : contiguous_container(initializer_list) {} - - stack(const contiguous_container& container) - : contiguous_container(container) {} - - [[nodiscard]] constexpr auto pop_back() -> type& { - return this->m_data[--this->m_size]; - } - - constexpr void pop() { - --this->m_size; - } - private: - - }; -} // utility diff --git a/source/utility/containers/string.h b/source/utility/containers/string.h deleted file mode 100644 index 4e2d8a8e..00000000 --- a/source/utility/containers/string.h +++ /dev/null @@ -1,72 +0,0 @@ -#pragma once -#include "utility/types.h" -#include "utility/containers/iterators/iterator.h" -#include "utility/containers/iterators/const_iterator.h" - -namespace utility { - class string { - public: - string() = default; - string(const char* value) - : m_string(value) {} - - string(const std::string& value) - : m_string(value) {} - - template - string(const std::format_string fmt, arguments&&... args) { - m_string.append(std::vformat(fmt.get(), std::make_format_args(args...))); - } - - ~string() = default; - - void operator+=(const string& other) { - m_string += other.m_string; - } - - template - void append( - const std::format_string fmt, - arguments&&... args - ) { - m_string.append(std::vformat(fmt.get(), std::make_format_args(args...))); - } - - void append(const string& other) { - m_string += other.get_underlying(); - } - - auto begin() -> std::string::iterator { - return m_string.begin(); - } - - auto end() -> std::string::iterator { - return m_string.end(); - } - - auto cbegin() const -> std::string::const_iterator { - return m_string.cbegin(); - } - - auto cend() const -> std::string::const_iterator { - return m_string.cend(); - } - - auto get_underlying() -> std::string { - return m_string; - } - - auto get_underlying() const -> const std::string& { - return m_string; - } - private: - std::string m_string; - }; -} - -namespace std { - inline ostream& operator<<(ostream& os, const utility::string& str) { - os << str.get_underlying(); - return os; - } -} diff --git a/source/utility/containers/string_accessor.cpp b/source/utility/containers/string_accessor.cpp deleted file mode 100644 index 6afebd33..00000000 --- a/source/utility/containers/string_accessor.cpp +++ /dev/null @@ -1,58 +0,0 @@ -#include "string_accessor.h" -#include "utility/macros.h" - -namespace utility::detail { - string_accessor::string_accessor(const std::string& string) - : m_string(string) {} - - void string_accessor::advance() { - m_position++; - } - - void string_accessor::retreat() { - m_position--; - } - - auto string_accessor::peek_next_char() const -> char { - ASSERT(m_position + 1 <= m_string.size(), "accessor out of range! (peek_next_char)"); - return m_string[m_position + 1]; - } - - auto string_accessor::get() const -> char { - // check if we are inside of our strings' bounds - if(m_position <= m_string.size()) { - return m_string[m_position]; - } - - // out-of-bounds access - return EOF; - } - - auto string_accessor::get_advance() -> char { - const char temp = get(); - advance(); - return temp; - } - - auto string_accessor::end() const -> bool { - return m_position > m_string.size(); - } - - auto string_accessor::get_data() const -> const std::string& { - return m_string; - } - - auto string_accessor::get_data() -> std::string& { - return m_string; - } - - auto string_accessor::get_position() const -> u64 { - return m_position; - } - - void string_accessor::set_position(u64 position) { - // check if we are inside of our strings' bounds - ASSERT(position <= m_string.size(), "accessor out of range! (set)"); - m_position = position; - } -} diff --git a/source/utility/containers/string_accessor.h b/source/utility/containers/string_accessor.h deleted file mode 100644 index dc28596d..00000000 --- a/source/utility/containers/string_accessor.h +++ /dev/null @@ -1,57 +0,0 @@ -#pragma once -#include "utility/types.h" - -namespace utility::detail { - /** - * \brief Utility string container class with support for caret movements. - */ - class string_accessor { - public: - string_accessor() = default; - - /** - * \brief Constructs the string accessor by filling it with the specified \a string. - * \param string String to use as the base of the string accessor - */ - string_accessor(const std::string& string); - - /** - * \brief Increments the caret location. - */ - void advance(); - - /** - * \brief Decrements the caret location. - */ - void retreat(); - - auto peek_next_char() const -> char; - - /** - * \brief Retrieves the character at the current caret location, if we are out of bounds an assertion is triggered. - * \returns Character at the current caret location - */ - [[nodiscard]] auto get() const -> char; - - /** - * \brief Retrieves the character at the current caret location and increments the caret location, if we are out of bounds an assertion is triggered - * \returns Character at the current caret location - */ - [[nodiscard]] auto get_advance() -> char; - - /** - * \brief Checks whether the current caret location is in/out of bounds of the contained string. - * \return True if the caret is out of bounds, otherwise False - */ - [[nodiscard]] auto end() const -> bool; - - [[nodiscard]] auto get_data() const -> const std::string&; - [[nodiscard]] auto get_data() -> std::string&; - [[nodiscard]] auto get_position() const -> u64; - - void set_position(u64 position); - private: - std::string m_string; // contained string - u64 m_position = 0; // current caret location - }; -} diff --git a/source/utility/containers/string_table.cpp b/source/utility/containers/string_table.cpp deleted file mode 100644 index 7fbcf702..00000000 --- a/source/utility/containers/string_table.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "string_table.h" -#include "utility/macros.h" - -namespace utility { - string_table_key::string_table_key() : m_value(0) {} - string_table_key::string_table_key(const std::string& string) - : m_value(std::hash{}(string)) {} - - auto string_table_key::operator==(string_table_key other) const -> bool { - return m_value == other.m_value; - } - - auto string_table_key::operator<(string_table_key other) const -> bool { - return m_value < other.m_value; - } - - auto string_table_key::get_value() const -> u64 { - return m_value; - } - - auto string_table_key::is_valid() const -> bool { - return m_value != 0; - } - - bool string_table::contains(string_table_key key) const { - return m_key_to_string.contains(key); - } - - auto string_table::insert(const std::string& string) -> string_table_key { - const string_table_key new_key(string); - - const auto it = m_key_to_string.find(new_key); - if (it != m_key_to_string.end()) { - // the key is already contained in the table - return new_key; - } - - m_key_to_string[new_key] = string; - return new_key; - } - - auto string_table::get(string_table_key key) const -> const std::string& { - ASSERT(key.get_value() != 0, "invalid symbol key"); - return m_key_to_string.at(key); - } -} // namespace utility diff --git a/source/utility/containers/string_table.h b/source/utility/containers/string_table.h deleted file mode 100644 index a2175c1b..00000000 --- a/source/utility/containers/string_table.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include "utility/types.h" - -namespace utility { - struct string_table_key { - string_table_key(); - string_table_key(const std::string& string); - - auto operator==(string_table_key other) const -> bool; - auto operator<(string_table_key other) const -> bool; - - auto get_value() const -> u64; - auto is_valid() const -> bool; - private: - u64 m_value; // already hashed - }; -} // namespace utility - -template <> -struct std::hash { - auto operator()(const utility::string_table_key& k) const noexcept -> utility::u64 { - // since we've already hashed the string in the constructor we can use the value - return k.get_value(); - } -}; - -namespace utility { - class string_table { - public: - string_table() = default; - - bool contains(string_table_key key) const; - auto insert(const std::string& string) -> string_table_key; - auto get(string_table_key key) const -> const std::string&; - private: - std::unordered_map m_key_to_string; - }; -} // namespace utility diff --git a/source/utility/format.h b/source/utility/format.h deleted file mode 100644 index 9ade212c..00000000 --- a/source/utility/format.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once -#include "types.h" - -namespace utility::detail { - template - struct formatter { - static std::string format(const type& value) { - std::ostringstream oss; - oss << value; - return oss.str(); - } - }; - - template - std::string format_argument( - uint64_t index, - tuple&& arguments, - std::index_sequence - ) { - std::string result; - const bool found = ((is == index && - (result = formatter>>>::format(std::get(std::forward(arguments))), true)) || ...); - - if (!found) { - throw std::runtime_error("format argument index out of range."); - } - - return result; - } - - template - std::string format( - const std::string& template_str, argument_types... args - ) { - std::ostringstream output; - u64 pos = 0; - u64 previous_pos = 0; - std::tuple arguments(args...); - - uint64_t index = 0; - while ((pos = template_str.find("{}", pos)) != std::string::npos) { - output << template_str.substr(previous_pos, pos - previous_pos); - output << format_argument(index, arguments, std::index_sequence_for{}); - pos += 2; // skip '{}' - previous_pos = pos; - ++index; - } - - output << template_str.substr(previous_pos); // append rest of the string - return output.str(); - } -} diff --git a/source/utility/memory.cpp b/source/utility/memory.cpp deleted file mode 100644 index b03dab1a..00000000 --- a/source/utility/memory.cpp +++ /dev/null @@ -1,85 +0,0 @@ -#include "memory.h" - -namespace utility { - byte::byte(u8 value) - : value(value) {} - - auto byte::to_hex() const -> std::string { - std::ostringstream oss; - oss << std::hex << std::setw(2) << std::setfill('0') << static_cast(value); - return oss.str(); - } - - auto byte::operator|=(const byte& other) -> byte { - value |= other.value; - return *this; - } - - auto byte::operator&=(const byte& other) -> byte { - value &= other.value; - return *this; - } - - auto byte::operator<<(i32 distance) const -> byte { - return byte(value << distance); - } - - auto byte::operator>>(i32 distance) const -> byte { - return byte(value >> distance); - } - - auto byte::operator+(const byte& other) const -> byte { - return value + other.value; - } - - byte::operator u8() const { - return value; - } - - auto align(u64 value, u64 alignment) -> u64 { - return value + (alignment - (value % alignment)) % alignment; - } - - auto sign_extend(u64 src, u64 src_bits, u64 dst_bits) -> u64 { - const u64 sign_bit = (src >> (src_bits - 1)) & 1; - - const u64 mask_dst_bits = ~UINT64_C(0) >> (64 - dst_bits); - const u64 mask_src_bits = ~UINT64_C(0) >> (64 - src_bits); - const u64 mask = mask_dst_bits & ~mask_src_bits; - - // initialize dst by zeroing out the bits from src_bits to dst_bits - const u64 dst = src & ~mask; - - // perform the actual sign extension - return dst | (sign_bit ? mask : 0); - } - - auto fits_into_8_bits(u64 value) -> bool { - return static_cast(value) <= std::numeric_limits::max(); - } - - auto fits_into_32_bits(u64 value) -> bool { - return static_cast(value) <= std::numeric_limits::max(); - } - - auto pop_count(u32 value) -> u8 { - return static_cast(std::bitset<32>(value).count()); - } - - auto ffs(i32 value) -> u8 { - if(value == 0) { - return 0; - } - - u8 pos = 1; - while(!(value & 1)) { - value >>= 1; - pos++; - } - - return pos; - } - auto is_power_of_two(u64 value) -> bool { - return (value & (value - 1)) == 0; - } -} \ No newline at end of file diff --git a/source/utility/memory.h b/source/utility/memory.h deleted file mode 100644 index 0f64ce7c..00000000 --- a/source/utility/memory.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once -#include "utility/diagnostics.h" - -namespace utility { - struct byte { - byte() = default; - byte(u8 value); - [[nodiscard]] auto to_hex() const-> std::string; - - auto operator |=(const byte& other) -> byte; - auto operator &=(const byte& other)->byte; - auto operator <<(i32 distance) const->byte; - auto operator >>(i32 distance) const->byte; - auto operator +(const byte& other) const->byte; - operator u8() const; - - u8 value; - }; - - /** - * \brief Aligns the specified \a value into blocks of the specified width. - * \param value Value to align - * \param alignment Alignment to use - * \return Aligned \a value. - */ - [[nodiscard]] auto align(u64 value, u64 alignment) -> u64; - - [[nodiscard]] auto sign_extend(u64 src, u64 src_bits, u64 dst_bits) -> u64; - - [[nodiscard]] auto fits_into_8_bits(u64 value) -> bool; - [[nodiscard]] auto fits_into_32_bits(u64 value) -> bool; - - [[nodiscard]] auto pop_count(u32 value) -> u8; - [[nodiscard]] auto ffs(i32 value) -> u8; - [[nodiscard]] auto is_power_of_two(u64 value) -> bool; -} diff --git a/source/utility/parametric/parametric.h b/source/utility/parametric/parametric.h deleted file mode 100644 index f4946166..00000000 --- a/source/utility/parametric/parametric.h +++ /dev/null @@ -1,859 +0,0 @@ -// Parametric: a basic and simple program options parser. -// Made by: @Goubermouche (https://github.com/Goubermouche) - -// Copyright(c) 2024 Goubermouche -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files(the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and /or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions : -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -#define PARAMETRIC_TABLE_WIDTH 60 - -namespace parametric { - constexpr inline int ERROR_RESULT = std::numeric_limits::max(); - - // default option parser overloads: - - template - struct options_parser { - static auto parse(const std::string& value) -> type { - return value; - } - }; - - template<> - struct parametric::options_parser { - static auto parse(const std::string& value) -> float { - return std::stof(value); - } - }; - - template<> - struct parametric::options_parser { - static auto parse(const std::string& value) -> double { - return std::stof(value); - } - }; - - template<> - struct parametric::options_parser { - static auto parse(const std::string& value) -> bool { - if (value == "true") { - return true; - } - - if (value == "false") { - return false; - } - - return static_cast(std::stoul(value)); - } - }; - - template<> - struct parametric::options_parser { - static auto parse(const std::string& value) -> uint8_t { - return static_cast(std::stoi(value)); - } - }; - - template<> - struct parametric::options_parser { - static auto parse(const std::string& value) -> uint16_t { - return static_cast(std::stoi(value)); - } - }; - - template<> - struct parametric::options_parser { - static auto parse(const std::string& value) -> uint32_t { - return std::stoul(value); - } - }; - - template<> - struct parametric::options_parser { - static auto parse(const std::string& value) -> uint64_t { - return std::stoull(value); - } - }; - - template<> - struct parametric::options_parser { - static auto parse(const std::string& value) -> int8_t { - return static_cast(std::stoi(value)); - } - }; - - template<> - struct parametric::options_parser { - static auto parse(const std::string& value) -> int16_t { - return static_cast(std::stoi(value)); - } - }; - - template<> - struct parametric::options_parser { - static auto parse(const std::string& value) -> int32_t { - return std::stoi(value); - } - }; - - template<> - struct parametric::options_parser { - static auto parse(const std::string& value) -> int64_t { - return std::stoll(value); - } - }; - - namespace detail { - /** - * \brief Checks if the specified \b value is a help flag (--help or -h). - * \param value Value to check - * \return True if the specified \b value is a help flag, false otherwise. - */ - inline auto is_help_flag(const std::string& value) -> bool { - return value == "--help" || value == "-h"; - } - - /** - * \brief Prints a block of strings aligned to \b start_offset and limited to a given max width - * by \b max_width. - * \param strings Strings to print - * \param start_offset Start offset to print the strings with - * \param max_width Max width of each line in the block - */ - inline void print_aligned_strings(const std::vector& strings, std::size_t start_offset, std::size_t max_width) { - std::size_t current_width = 0; - - for (const auto& string : strings) { - current_width += string.size(); - - if (current_width > max_width) { - // print a newline and align to the beginning of the line above - std::cout << "\n" << std::string(start_offset, ' '); - current_width = string.size(); - } - - std::cout << string; - } - } - - /** - * \brief Checks if a given \b value is a verbose flag (whether it begins with two dashes). - * \param value Value to check - * \return True if the specified \b value is a verbose flag, false otherwise. - */ - inline auto is_verbose_flag(const std::string& value) -> bool { - // NOTE: this isn't a smart function, but it handles everything correctly for - // our needs - - if (value.length() < 2) { - return false; - } - - return value[0] == '-' && value[1] == '-'; - } - - /** - * \brief Checks if a given \b value is a flag (whether it begins with a dash). - * \param value Value to check - * \return True if the specified \b value is a flag, false otherwise. - */ - inline auto is_flag(const std::string& value) -> bool { - // NOTE: this isn't a smart function, but it handles everything correctly for - // our needs - - if (value.empty()) { - return false; - } - - return value[0] == '-'; - } - - /** - * \brief Removes \b up \b to \b two leading dashes from a given string. - * \param input Input string - * \param output Output string - * \return True if the operation succeeded (if we were able to remove at most two dashes), - * false otherwise (either the string is invalid, or we were able to remove more than two - * dashes). - */ - inline auto remove_leading_dashes(const std::string& input, std::string& output) -> bool { - std::size_t dash_count = 0; - - while (dash_count < input.size() && input[dash_count] == '-') { - ++dash_count; - } - - if (dash_count > 2) { - return false; - } - - output = input.substr(dash_count); - return true; - } - - /** - * \brief A basic abstraction which allows us to have a map with 1 or 2 keys pointing - * to the same value. - * \tparam key_type Type of the two keys - * \tparam value_type Type of the stored value - */ - template - class dual_map { - using iterator = typename std::map::iterator; - using const_iterator = typename std::map::const_iterator; - public: - auto insert(const key_type& primary_key, const value_type& value) -> value_type& { - auto& val_ref = primary_map[primary_key]; - val_ref = value; - return val_ref; - } - - auto insert(const key_type& primary_key, const key_type& secondary_key, const value_type& value) -> value_type& { - auto& val_ref = primary_map[primary_key]; - val_ref = value; - secondary_to_primary_map[secondary_key] = primary_key; - return val_ref; - } - - auto begin() -> iterator { return primary_map.begin(); } - auto end() -> iterator { return primary_map.end(); } - - auto begin() const -> const_iterator { return primary_map.begin(); } - auto end() const -> const_iterator { return primary_map.end(); } - - auto cbegin() const -> const_iterator { return primary_map.cbegin(); } - auto cend() const-> const_iterator { return primary_map.cend(); } - - auto find(const key_type& key) -> iterator { - auto primary_it = primary_map.find(key); - if (primary_it != primary_map.end()) { - return primary_it; - } - - auto secondary_it = secondary_to_primary_map.find(key); - if (secondary_it != secondary_to_primary_map.end()) { - return primary_map.find(secondary_it->second); - } - - return end(); - } - - auto find(const key_type& key) const -> const_iterator { - auto primary_it = primary_map.find(key); - if (primary_it != primary_map.end()) { - return primary_it; - } - - auto secondary_it = secondary_to_primary_map.find(key); - if (secondary_it != secondary_to_primary_map.end()) { - return primary_map.find(secondary_it->second); - } - - return end(); - } - - auto empty() const -> bool { - return primary_map.empty(); - } - - auto size() const -> std::size_t { - return primary_map.size(); - } - private: - std::map primary_map; - std::unordered_map secondary_to_primary_map; - }; - - // Basic wrappers for erasing the type of our store values - - struct value_wrapper_interface { - virtual auto parse(const std::string& value) -> std::any = 0; - virtual ~value_wrapper_interface() = default; - - // used for cases when the value is implicit (ie. pure flags) - bool expects_value = true; - }; - - template - struct value_wrapper : value_wrapper_interface { - auto parse(const std::string& value) -> std::any override { - return options_parser::parse(value); - } - }; - - using value_wrapper_ptr = std::shared_ptr; - } // namespace detail - - /** - * \brief Pack of parameters which were extracted from userland arguments. - */ - class parameters { - public: - /** - * \brief Checks whether the parameter pack contains a given key. - * \param name Key to check - * \return True if the pack contains the given key, false otherwise - */ - auto contains(const std::string& name) const -> bool { - return m_values.contains(name); - } - - /** - * \brief Retrieves a given key from the pack. - * \tparam type Type of the given key - * \param name Name of the given key - * \return Value of type \b type held under \b key. - */ - template - auto get(const std::string& name) const -> type { - return std::any_cast(m_values.at(name)); - } - private: - std::unordered_map m_values; - - friend class flag; - friend class positional_argument; - }; - - /** - * \brief Context which is passed around when parsing the parameter tree. - */ - struct context { - context(int argc, char** argv) : arguments(argv, argv + argc) { - arguments[0] = std::filesystem::path(argv[0]).filename().string(); - } - - auto can_get_next_value() const -> bool { - return argument_index + 1 <= arguments.size(); - } - - auto get_next_value() -> const std::string& { - return arguments[argument_index++]; - } - - void begin_new_scope() { - m_scoped_argument_index = argument_index; - } - - /** - * \brief Creates a string representing all values from \a arguments [0] \a - - * \a arguments[\a scoped \a argument \a index] separated by a space (' ') char. - * \return String representation of our argument range. - */ - auto get_value_string() const -> std::string { - std::ostringstream oss; - - for (std::size_t i = 0; i < m_scoped_argument_index && i < arguments.size(); ++i) { - if (i > 0) { - oss << ' '; - } - - oss << arguments[i]; - } - - return oss.str(); - } - - std::vector arguments; // userland arguments - - std::size_t argument_index = 1; // local argument index used when parsing - std::size_t positional_argument_index = 0; // index of our positional arguments - - // final output parameters, which are incrementally constructed when parsing - parameters output_parameters; - private: - // helper index which is updated when a new scope (command/command group) is created - // used for clearer help prints - std::size_t m_scoped_argument_index = 0; - }; - - /** - * \brief Represents a simple flag (either --flag or -f). A flag can either implicitly represent - * a value (when it's declared with a bool type), or a flag which requires a succeeding value. - * When using a custom type a parametric::options_parser overload for the given type must be - * defined. - */ - class flag { - public: - flag() = default; - flag(const std::string& long_name, const std::string& description, const std::string& short_name, const detail::value_wrapper_ptr& value_interface) - : m_long_name(long_name), m_short_name(short_name), m_description(description), m_value_interface(value_interface) {} - private: - auto parse_next(context& context, const std::string& provided_flag_name, const std::string& provided_flag_name_no_dashes) -> int { - // check dash-correctness - // since we know our flag has at most two dashes, we don't have to do error checking here - if (detail::is_flag(provided_flag_name)) { - const bool is_verbose = detail::is_verbose_flag(provided_flag_name); - - if ((m_long_name == provided_flag_name_no_dashes) != is_verbose) { - if (m_short_name.empty()) { - // missing long alias - std::cout << std::format("error: invalid flag verbosity '{}' (did you mean to use '--{}'?)\n", provided_flag_name, m_long_name); - } - else { - // invalid short/long alias - std::cout << std::format("error: invalid flag verbosity '{}' (did you mean to use '-{}' or '--{}'?)\n", provided_flag_name, m_short_name, m_long_name); - } - - return ERROR_RESULT; - } - } - else { - // not a flag - std::cout << std::format("error: invalid flag format '{}' (did you mean to use {}?)\n", provided_flag_name, get_label()); - return ERROR_RESULT; - } - - // parse the final value - if (m_value_interface->expects_value) { - if (!context.can_get_next_value()) { - std::cout << std::format("error: missing value for flag '{}'\n", provided_flag_name); - return ERROR_RESULT; - } - - const std::string value = context.get_next_value(); - - try { - context.output_parameters.m_values[m_long_name] = m_value_interface->parse(value); - } - catch (...) { - std::cout << std::format("error: invalid format for flag '{}'\n", provided_flag_name); - return ERROR_RESULT; - } - } - else { - // pure flag, mark it - context.output_parameters.m_values[m_long_name] = true; - } - - return 0; - } - - auto get_label() const -> std::string { - if (!m_short_name.empty()) { - return std::format("[--{} | -{}]", m_long_name, m_short_name); - } - - return std::format("[--{}]", m_long_name); - } - - void use_default_if_unused(context& context) const { - if (!m_default_value.has_value()) { - return; - } - - if (!context.output_parameters.contains(m_long_name)) { - context.output_parameters.m_values[m_long_name] = m_default_value; - } - } - private: - // info - std::string m_long_name; - std::string m_short_name; - std::string m_description; - - // parse function used when handling value conversions - detail::value_wrapper_ptr m_value_interface; - - std::any m_default_value; // optional default value - - friend class command; - }; - - /** - * \brief Represents a simple positional argument. Positional arguments are \b mandatory and - * should always be specified in a given order. Each argument maps is guaranteed to map to a - * value in \b parameters. When using a custom type a parametric::options_parser overload for - * the given type must be defined. - */ - class positional_argument { - public: - positional_argument() = default; - positional_argument(const std::string& name, const std::string& description, const detail::value_wrapper_ptr& value_interface) - : m_name(name), m_description(description), m_value_interface(value_interface) {} - private: - auto parse_next(context& context) const -> int { - const std::string value = context.arguments[context.argument_index - 1]; - - try { - context.output_parameters.m_values[m_name] = m_value_interface->parse(value); - } - catch (...) { - std::cout << std::format("error: invalid format for positional argument '{}'\n", m_name); - return ERROR_RESULT; - } - - return 0; - } - private: - // info - std::string m_name; - std::string m_description; - - // parse function used when handling value conversions - detail::value_wrapper_ptr m_value_interface; - - friend class command; - }; - - class command_base { - public: - command_base() = default; - command_base(const std::string& description) : m_description(description) {} - - virtual ~command_base() = default; - - protected: - virtual void print_help(context& context) = 0; - virtual auto parse_next(context& context) -> int = 0; - protected: - std::string m_description; - - friend class command_group; - }; - - /** - * \brief Represents a basic command (such as 'add' in 'git add'). Each command is the final unit - * of logic before userland code is executed. Once a command is run be the user the specified - * function is executed and the parsed parameters are provided as the parameter. - */ - class command : public command_base { - public: - using function = std::function; - - command() = default; - command(const std::string& name, const std::string& description, const function& func) - : command_base(description), m_name(name), m_function(func) {} - - /** - * \brief Appends a new flag to the command. - * \tparam type Type of the flag (defaults to bool, if any other type is specified the flag will - * also expect a succeeding value to be specified) - * \param long_name Long flag alias (--flag) - * \param description Description of the flag - * \param short_name Short flag alias (-f) - * \return Reference to the newly created flag. - */ - template - auto add_flag(const std::string& long_name, const std::string& description, const std::string& short_name = "") -> flag& { - auto value_interface = std::make_shared>(); - - flag new_flag(long_name, description, short_name, value_interface); - - // handle booleans as flags which don't require a value - if constexpr (std::is_same_v) { - value_interface->expects_value = false; - new_flag.m_default_value = false; - } - - if (!short_name.empty()) { - return m_flags.insert(long_name, short_name, new_flag); - } - - return m_flags.insert(long_name, new_flag); - } - - /** - * \brief Appends a new flag to the command and assigns its default value. - * \tparam type Type of the flag (defaults to bool, if any other type is specified the flag will - * also expect a succeeding value to be specified) - * \param long_name Long flag alias (--flag) - * \param description Description of the flag - * \param short_name Short flag alias (-f) - * \param default_value Default value which will be used if the flag isn't mentioned by the user - * \return Reference to the newly created flag. - */ - template - auto add_flag(const std::string& long_name, const std::string& description, const std::string& short_name, const type& default_value) -> flag& { - flag& flag = add_flag(long_name, description, short_name); - - // assign the expected value - flag.m_default_value = default_value; - - return flag; - } - - /** - * \brief Appends a new positional argument. Positional arguments need to be specified before any - * flags, but after commands. \b Positional \b arguments \b are \b mandatory and may not be ignored - * by the user. The order of individual positional arguments is the same as the order in which the - * add_positional_argument was called. - * \tparam type Type the argument should be parsed as - * \param name Name of the positional argument - * \param description Description of the positional argument - */ - template - void add_positional_argument(const std::string& name, const std::string& description) { - m_arguments.emplace_back(name, description, std::make_shared>()); - } - private: - auto parse_next(context& context) -> int override { - context.begin_new_scope(); // push a new scope - - // parse all remaining options - while (context.can_get_next_value()) { - std::string value = context.get_next_value(); - - // if the current key is a help flag - if (detail::is_help_flag(value)) { - print_help(context); - return ERROR_RESULT; - } - - // prefer parsing positional arguments first - if (context.positional_argument_index < m_arguments.size()) { - if (m_arguments[context.positional_argument_index++].parse_next(context) != ERROR_RESULT) { - continue; - } - - return ERROR_RESULT; // handled by the parse_next call - } - - // check against our flag list - std::string value_no_dashes; - - if (!detail::remove_leading_dashes(value, value_no_dashes)) { - // our string has too many dashes, exit - std::cout << std::format("error: unknown argument '{}'", value); - print_help(context); - return ERROR_RESULT; - } - - // locate the flag - const auto flag_it = m_flags.find(value_no_dashes); - if (flag_it != m_flags.end()) { - if (flag_it->second.parse_next(context, value, value_no_dashes) != ERROR_RESULT) { - continue; - } - - return ERROR_RESULT; // handled by the parse_next call - } - - std::cout << std::format("error: unknown argument '{}'\n", value); - print_help(context); - return ERROR_RESULT; - } - - // require all positional arguments to be specified - if (context.positional_argument_index != m_arguments.size()) { - std::cout << std::format("error: missing positional argument/s for command '{}'\n", m_name); - print_help(context); - return ERROR_RESULT; - } - - // at this point, we've parse_next all of our arguments - // check against default arguments - for (const auto& [name, flag] : m_flags) { - flag.use_default_if_unused(context); - } - - // run our function - return m_function(context.output_parameters); - } - - void print_help(context& context) override { - const std::string value_string = context.get_value_string(); - std::cout << std::format("usage: {} ", value_string); - - // print a list of available arguments/flags - std::vector labels; - labels.reserve(m_arguments.size() + m_flags.size()); - - for (const auto& arg : m_arguments) { - labels.emplace_back(std::format("<{}> ", arg.m_name)); - } - - for (const auto& [name, flag] : m_flags) { - labels.emplace_back(flag.get_label() + " "); - } - - detail::print_aligned_strings(labels, value_string.size() + 8, PARAMETRIC_TABLE_WIDTH); - std::cout << "\n"; - - // print a description of available arguments/flags - if (!m_arguments.empty() || !m_flags.empty()) { - std::cout << '\n'; - } - - // find the longest argument/flag name - std::size_t longest_name = 0; - - for (const auto& arg : m_arguments) { - longest_name = std::max(arg.m_name.size(), longest_name); - } - - for (const auto& [name, flag] : m_flags) { - longest_name = std::max(name.size(), longest_name); - } - - // handle arguments - for (const auto& arg : m_arguments) { - std::cout << std::format(" {:<{}s}{}\n", arg.m_name, longest_name + 3, arg.m_description); - } - - if (!m_flags.empty() && !m_arguments.empty()) { - std::cout << '\n'; - } - - // handle flags - for (const auto& [name, flag] : m_flags) { - std::cout << std::format(" {:<{}s}{}\n", name, longest_name + 3, flag.m_description); - } - } - private: - std::string m_name; // name of the command - function m_function; // function to invoke when the command is successfully executed - - detail::dual_map m_flags; // command flags - std::vector m_arguments; // list of positional arguments, stored in order of appearances - }; - - /** - * \brief Grouping of commands, can either hold commands or other command groups. A command - * group may not be executed on its own and serves only as a wrapper for more commands or - * command groups. - */ - class command_group : public command_base { - public: - command_group() = default; - command_group(const std::string& description) : command_base(description) {} - - /** - * \brief Appends a new child command group. - * \param name Name of the new command group - * \param description Description of the new command group - * \return Reference to the new command group. - */ - command_group& add_command_group(const std::string& name, const std::string& description) { - const auto group = std::make_shared(description); - m_commands[name] = group; - return *group; - } - - /** - * \brief Appends a new child command. - * \param name Name of the new command - * \param description Description of the new command - * \param func Function which will be executed if the command is chosen by the user - * \return Reference to the new command. - */ - command& add_command(const std::string& name, const std::string& description, const command::function& func) { - const auto cmd = std::make_shared(name, description, func); - m_commands[name] = cmd; - return *cmd; - } - protected: - auto parse_next(context& context) -> int override { - context.begin_new_scope(); // push a new scope - - // verify that we have a command key - if (!context.can_get_next_value()) { - std::cout << "error: missing command\n"; - print_help(context); - return ERROR_RESULT; - } - - std::string value = context.get_next_value(); - - // if the current key is a help flag - if (detail::is_help_flag(value)) { - print_help(context); - return ERROR_RESULT; - } - - // look for a command/command group - const auto command_it = m_commands.find(value); - if (command_it != m_commands.end()) { - return command_it->second->parse_next(context); - } - - std::cout << std::format("error: unknown command '{}'\n", value); - print_help(context); - return ERROR_RESULT; - } - - void print_help(context& context) override { - const std::string value_string = context.get_value_string(); - std::cout << std::format("usage: {} ", value_string); - - // print a list of available subcommands - std::vector labels; - labels.reserve(m_commands.size()); - - for (const auto& [name, subcommand] : m_commands) { - labels.emplace_back(std::format("[{}] ", name)); - } - - detail::print_aligned_strings(labels, value_string.size() + 8, PARAMETRIC_TABLE_WIDTH); - std::cout << "\n\n"; - - // find the longest subcommand name - std::size_t longest_name = 0; - - for (const auto& [name, subcommand] : m_commands) { - longest_name = std::max(name.size(), longest_name); - } - - // print all subcommands - // name<--padding-->description - for (const auto& [name, subcommand] : m_commands) { - std::cout << std::format(" {:<{}s}{}\n", name, longest_name + 3, subcommand->m_description); - } - } - protected: - std::map> m_commands; // child commands/command groups - }; - - /** - * \brief Virtual representation of a program and it's arguments/runtime. - */ - class program : public command_group { - public: - /** - * \brief Parses the specified program parameters (\b argc, \b argv), and calls the respective function of - * the chosen command. - * \param argc Count of provided arguments - * \param argv Argument values - * \return Return code returned by the function of the chosen program. If the help menu is show to the user - * the program exits with '0'. - */ - auto parse(int argc, char** argv) -> int { - context ctx(argc, argv); - int result = parse_next(ctx); - - // since we don't actually want to return ERROR_RESULT, we override it to '0' - if (result == ERROR_RESULT) { - result = 0; - } - - return result; - } - }; -} // namespace parametric diff --git a/source/utility/shell.cpp b/source/utility/shell.cpp deleted file mode 100644 index e17b8927..00000000 --- a/source/utility/shell.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "shell.h" - -namespace utility { - auto shell::execute(const std::string& command) -> i32 { -#ifdef SYSTEM_WINDOWS - const std::string windows_command = "cmd /c " + command; - return system(windows_command.c_str()); -#elif defined SYSTEM_LINUX - i32 status = system(command.c_str()); - if (WIFEXITED(status)) { - return WEXITSTATUS(status); - } - else { - // abnormal termination - return -1; - } -#else - PANIC("unsupported platform detected"); -#endif - } - - auto shell::open_link(const std::string& link) -> i32 { - std::string command; -#ifdef SYSTEM_WINDOWS - command = "start " + link; -#elif defined SYSTEM_LINUX - command = "xdg-open " + link; -#else - PANIC("unsupported platform detected"); -#endif - - return execute(command); - } -} // namespace utility diff --git a/source/utility/shell.h b/source/utility/shell.h deleted file mode 100644 index 8a074464..00000000 --- a/source/utility/shell.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once -#include "utility/macros.h" - -namespace utility { - /** - * \brief Basic shell abstraction, used for invoking commands on the local system. - */ - struct shell { - /** - * \brief Runs the specified \b command in the local shell. - * \param command Command to run - * \return 0 if all operations succeeded, non-zero otherwise. - */ - static auto execute(const std::string& command) -> i32; - - /** - * \brief Opens the specified \b link in the local browser. - * \param link Link to open - * \return 0 if all operations succeeded, non-zero otherwise. - */ - static auto open_link(const std::string& link) -> i32; - }; -} // namespace utility diff --git a/source/utility/string_helper.cpp b/source/utility/string_helper.cpp deleted file mode 100644 index 37fa3a68..00000000 --- a/source/utility/string_helper.cpp +++ /dev/null @@ -1,106 +0,0 @@ -#include "string_helper.h" - -namespace utility::detail { - auto format_ending(u64 count, const std::string& singular, const std::string& plural) -> std::string { - return count == 1 ? singular : plural; - } - - auto create_caret_underline(const std::string& str, u64 start, u64 end) -> std::string { - std::string underline; - - for (u64 i = 0; i < str.length(); i++) { - - if (i >= start && i < end) { - underline += "^"; - } - else { - // account for tabs, newlines, and carriage returns - switch (str[i]) { - case '\t': underline += "\t"; break; // tab - case '\n': underline += "\n"; break; // newline - case '\r': underline += "\r"; break; // carriage return - default: underline += " "; break; // other characters - } - } - } - - return underline; - } - - bool is_hex(char c) { - return std::isdigit(c) || (std::tolower(c) >= 'a' && std::tolower(c) <= 'f'); - } - - bool is_bin(char c) { - return c == '0' || c == '1'; - } - - auto escape_string(const std::string& input) -> std::string { - // todo: update to support various hexadecimal and binary strings - std::string output; - for (const char ch : input) { - if (ch == '\\' || ch == '\'' || ch == '\"' || ch == '\a' || ch == '\b' || ch == '\f' || ch == '\n' || ch == '\r' || ch == '\t' || ch == '\v' || ch == '\x1b') { - output.push_back('\\'); - switch (ch) { - case '\\': output.push_back('\\'); break; - case '\'': output.push_back('\''); break; - case '\"': output.push_back('\"'); break; - case '\a': output.push_back('a'); break; - case '\b': output.push_back('b'); break; - case '\f': output.push_back('f'); break; - case '\n': output.push_back('n'); break; - case '\r': output.push_back('r'); break; - case '\t': output.push_back('t'); break; - case '\v': output.push_back('v'); break; - case '\x1b': - output.append("x1b"); - break; - } - } - else { - output.push_back(ch); - } - } - return output; - } - - auto remove_leading_spaces(const std::string& str) -> std::pair { - u64 first_non_space = 0; - - while (first_non_space < str.size() && std::isspace(str[first_non_space])) { - ++first_non_space; - } - - return { first_non_space, str.substr(first_non_space) }; - } - - void string_replace(std::string& str, const std::string& from, const std::string& to) { - u64 start_pos = 0; - while ((start_pos = str.find(from, start_pos)) != std::string::npos) { - str.replace(start_pos, from.length(), to); - start_pos += to.length(); - } - } - - auto remove_first_line(const std::string& string) -> std::string { - const u64 pos = string.find('\n'); - - if (pos != std::string::npos) { - // create a substring from the character after the newline - return string.substr(pos + 1); - } - - // no newline found, return the original string or an empty string - return string; - } - - auto is_only_char(const std::string& s, char c) -> bool { - for (const char ch : s) { - if (ch != c) { - return false; - } - } - - return true; - } -} // namespace sigma diff --git a/source/utility/timer.cpp b/source/utility/timer.cpp deleted file mode 100644 index e29e3d44..00000000 --- a/source/utility/timer.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "timer.h" - -namespace utility { - void timer::start() { - m_start = std::chrono::high_resolution_clock::now(); - } - - f64 timer::elapsed_seconds() const { - return elapsed>(); - } -} \ No newline at end of file diff --git a/source/utility/timer.h b/source/utility/timer.h deleted file mode 100644 index 2faf6e9c..00000000 --- a/source/utility/timer.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once -#include "macros.h" - -namespace utility { - class timer { - public: - void start(); - - template - [[nodiscard]] f64 elapsed() const; - - [[nodiscard]] f64 elapsed_seconds() const; - private: - std::chrono::high_resolution_clock::time_point m_start; - }; - - template - f64 timer::elapsed() const { - const auto now = std::chrono::high_resolution_clock::now(); - const auto diff = now - m_start; - return std::chrono::duration_cast(diff).count(); - } -} diff --git a/source/utility/types.h b/source/utility/types.h deleted file mode 100644 index 04707d6d..00000000 --- a/source/utility/types.h +++ /dev/null @@ -1,122 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// platform specific -#ifdef _WIN32 -#define NOMINMAX - #include - #include -#else - #include - #include - #include -#endif - -namespace utility { - namespace types { - // signed integers - using i8 = int8_t; - using i16 = int16_t; - using i32 = int32_t; - using i64 = int64_t; - - // unsigned integers - using u8 = uint8_t; - using u16 = uint16_t; - using u32 = uint32_t; - using u64 = uint64_t; - - // floating point - using f32 = float; - using f64 = double; - - using ptr_diff = ptrdiff_t; - - template - using s_ptr = std::shared_ptr; - - template - using u_ptr = std::unique_ptr; - } // namespace utility::types - - using namespace types; - - template - auto num_digits(type number) -> u8 { - u8 digits = 0; - - while (number) { - number /= 10; - digits++; - } - - return digits; - } - - namespace detail { - template - struct has_const_iterator { - private: - typedef char yes; - typedef struct { char array[2]; } no; - - template static yes test(typename container_type::const_iterator*); - template static no test(...); - public: - static const bool value = sizeof(test(0)) == sizeof(yes); - }; - - template - struct has_begin_end { - template static char(&f(typename std::enable_if< - std::is_same(&container_type::begin)), - typename container_type::const_iterator(container_type::*)() const>::value, void>::id*))[1]; - - template static char(&f(...))[2]; - - template static char(&g(typename std::enable_if< - std::is_same(&container_type::end)), - typename container_type::const_iterator(container_type::*)() const>::value, void>::id*))[1]; - - template static char(&g(...))[2]; - - static bool const beg_value = sizeof(f(0)) == 1; - static bool const end_value = sizeof(g(0)) == 1; - }; - - template - struct is_container : std::integral_constant::value&& has_begin_end::beg_value&& has_begin_end::end_value> {}; - } // namespace detail -} // namespace utility diff --git a/source/utility/containers/allocators/block_allocator.h b/source/utility/util/block_allocator.h similarity index 51% rename from source/utility/containers/allocators/block_allocator.h rename to source/utility/util/block_allocator.h index e67af24a..549fc8eb 100644 --- a/source/utility/containers/allocators/block_allocator.h +++ b/source/utility/util/block_allocator.h @@ -1,11 +1,14 @@ #pragma once -#include "utility/memory.h" +#include "macros.h" +#include "memory.h" namespace utility { class block_allocator { struct block { - block(u8* memory); - ~block(); + block(u8* memory) : memory(memory), position(0) {} + ~block() { + std::free(memory); + } u8* memory; u64 position; @@ -16,8 +19,18 @@ namespace utility { * \brief Constructs a new block allocator. * \param block_size Size of individual blocks [bytes] */ - block_allocator(u64 block_size); - ~block_allocator(); + block_allocator(u64 block_size) : m_block_size(block_size) { + allocate_block(); // allocate the first block + m_first_block = m_current_block; + } + + ~block_allocator() { + while (m_first_block) { + const block* temp = m_first_block; + m_first_block = m_first_block->next; + delete temp; + } + } block_allocator(const block_allocator& other) = delete; block_allocator(block_allocator&& other) = delete; @@ -28,26 +41,81 @@ namespace utility { /** * \brief Prints owned blocks and their contents. */ - void print_bytes() const; + void print_bytes() const { + const u8 digit_count = num_digits(get_block_count()) + 3; + const block* temp = m_first_block; + u64 index = 0; + + while (temp) { + console::print("{:<{}}", std::format("{}:", index++), digit_count); + + for (u64 i = 0; i < temp->position; i++) { + console::print("{:02X} ", temp->memory[i]); + } + + console::print("\n"); + temp = temp->next; + } + } /** * \brief Prints % of used memory for every block. */ - void print_used() const; + void print_used() const { + const u8 digit_count = num_digits(get_block_count()) + 3; + const double percent = static_cast(m_block_size) / 100.0; + const block* temp = m_first_block; + + u64 wasted_bytes = 0; + u64 index = 0; + + while (temp) { + double used_percent = static_cast(temp->position) / percent; + wasted_bytes += m_block_size - temp->position; + temp = temp->next; + + console::print("{:<{}}{:.2f}%\n", std::format("{}:", index++), digit_count, used_percent); + } + + const u64 total_bytes = get_block_count() * m_block_size; + const double total_percent = static_cast(total_bytes) / 100.0; + const double wasted_percent = static_cast(wasted_bytes) / total_percent; + + console::print("\nwasted {}B / {}B ({:.2f}%)\n", wasted_bytes, total_bytes, wasted_percent); + } /** * \brief Allocates \b size bytes of uninitialized memory. * \param size Amount of memory to allocate [bytes] * \return Pointer to the beginning of the allocated region. */ - auto allocate(u64 size) -> void*; + auto allocate(u64 size) -> void* { + ASSERT(size <= m_block_size, "insufficient block size for allocation of {}B", size); + + if (size == 0) { + return nullptr; + } + + // if this allocation incurs a buffer overflow allocate a new block + if (m_current_block->position + size >= m_block_size) { + allocate_block(); + } + + void* memory = m_current_block->memory + m_current_block->position; + m_current_block->position += size; + return memory; + } /** * \brief Allocates \b size bytes of zero-initialized memory. * \param size Amount of memory to allocate [bytes] * \return Pointer to the beginning of the allocated region. */ - auto allocate_zero(u64 size) -> void*; + auto allocate_zero(u64 size) -> void* { + void* memory = allocate(size); + std::memset(memory, 0, size); + return memory; + } /** * \brief Allocates sizeof(type) bytes of uninitialized memory. @@ -93,18 +161,32 @@ namespace utility { * \brief Retrieves the current amount of allocated blocks. * \return Count of currently allocated blocks. */ - auto get_block_count() const->u64; + auto get_block_count() const -> u64 { + return m_block_count; + } /** * \brief Retrieves the max size of individual blocks [bytes]. * \return Max size of individual blocks [bytes]. */ - auto get_block_size() const -> u64; + auto get_block_size() const -> u64 { + return m_block_size; + } private: /** * \brief Helper function for allocating new memory blocks */ - void allocate_block(); + void allocate_block() { + const auto memory = static_cast(std::malloc(m_block_size)); + const auto new_block = new block(memory); + + if (m_current_block) { + m_current_block->next = new_block; + } + + m_current_block = new_block; + m_block_count++; + } private: block* m_first_block = nullptr; block* m_current_block = nullptr; @@ -112,4 +194,4 @@ namespace utility { u64 m_block_size; u64 m_block_count = 0; }; -} +} // namespace utility diff --git a/source/utility/containers/byte_buffer.h b/source/utility/util/containers/byte_buffer.h similarity index 94% rename from source/utility/containers/byte_buffer.h rename to source/utility/util/containers/byte_buffer.h index 32de9524..33d5ab02 100644 --- a/source/utility/containers/byte_buffer.h +++ b/source/utility/util/containers/byte_buffer.h @@ -1,24 +1,24 @@ #pragma once -#include "utility/containers/contiguous_container.h" -#include "utility/memory.h" +#include "contiguous_buffer.h" +#include "../memory.h" namespace utility { /** * \brief Simple abstraction for a list of bytes. Provides basic quality of life * features such as resolving relocations and iterators. */ - class byte_buffer : public contiguous_container { + class byte_buffer : public contiguous_buffer { public: byte_buffer() = default; - byte_buffer(const contiguous_container& container) - : contiguous_container(container) {} + byte_buffer(const contiguous_buffer& container) + : contiguous_buffer(container) {} byte_buffer(const slice& slice) - : contiguous_container(slice.get_size(), slice.get_data()) {} + : contiguous_buffer(slice.get_size(), slice.get_data()) {} byte_buffer(u64 size) - : contiguous_container(size) {} + : contiguous_buffer(size) {} auto operator[](u64 index) const -> const byte& { return m_data[index]; @@ -35,7 +35,7 @@ namespace utility { auto get_data() const -> const byte* { return m_data; } - + /** * \brief Appends a byte of data to the buffer. * \param data Data to append diff --git a/source/utility/containers/contiguous_container.h b/source/utility/util/containers/contiguous_buffer.h similarity index 92% rename from source/utility/containers/contiguous_container.h rename to source/utility/util/containers/contiguous_buffer.h index a2683805..2522585b 100644 --- a/source/utility/containers/contiguous_container.h +++ b/source/utility/util/containers/contiguous_buffer.h @@ -1,33 +1,32 @@ #pragma once -#include "utility/macros.h" -#include "utility/containers/slice.h" -#include +#include "../macros.h" +#include "slice.h" namespace utility { template - class contiguous_container { + class contiguous_buffer { public: - constexpr contiguous_container() + constexpr contiguous_buffer() : m_owning(true), m_data(nullptr), m_size(0), m_capacity(0) {} - constexpr contiguous_container(u64 size) + constexpr contiguous_buffer(u64 size) : m_owning(true), m_data(allocate(size)), m_size(size), m_capacity(size) {} - constexpr contiguous_container(u64 size, type* data) + constexpr contiguous_buffer(u64 size, type* data) : m_owning(false), m_data(data), m_size(size), m_capacity(size) {} - constexpr contiguous_container(std::initializer_list initializer_list) + constexpr contiguous_buffer(std::initializer_list initializer_list) : m_owning(true), m_data(allocate(initializer_list.size())), m_size(initializer_list.size()), m_capacity(m_size) { std::uninitialized_copy(initializer_list.begin(), initializer_list.end(), m_data); } - contiguous_container(const contiguous_container& other) + contiguous_buffer(const contiguous_buffer& other) : m_owning(true), m_size(other.m_size), m_capacity(other.m_capacity) { m_data = static_cast(std::malloc(m_capacity * sizeof(type))); std::memcpy(m_data, other.m_data, m_capacity * sizeof(type)); } - contiguous_container& operator=(const contiguous_container& other) { + contiguous_buffer& operator=(const contiguous_buffer& other) { if(&other == this) { return *this; } @@ -41,7 +40,7 @@ namespace utility { return *this; } - constexpr ~contiguous_container() { + constexpr ~contiguous_buffer() { if (m_owning) { if (!std::is_trivial_v) { destruct_range(begin(), end()); @@ -57,8 +56,8 @@ namespace utility { * \param size Target container size * \return 0-filled contiguous container of the specified \b size. */ - [[nodiscard]] static contiguous_container zero_initialize(u64 size) { - const contiguous_container container(size); + [[nodiscard]] static contiguous_buffer zero_initialize(u64 size) { + const contiguous_buffer container(size); container.zero_fill(); return container; } @@ -68,8 +67,8 @@ namespace utility { * \param capacity Target container capacity * \return Pre-reserved contiguous container. */ - [[nodiscard]] static contiguous_container reserve_initialize(u64 capacity) { - contiguous_container container; + [[nodiscard]] static contiguous_buffer reserve_initialize(u64 capacity) { + contiguous_buffer container; container.reserve(capacity); return container; } @@ -225,7 +224,7 @@ namespace utility { * \brief Appends a contiguous container to the end of this container. * \param other Container to append */ - constexpr void append(const contiguous_container& other) { + constexpr void append(const contiguous_buffer& other) { insert(end(), other.begin(), other.end()); } @@ -233,7 +232,7 @@ namespace utility { * \brief Prepends a contiguous container to the beginning of this container. * \param other Container to prepend */ - constexpr void prepend(const contiguous_container& other) { + constexpr void prepend(const contiguous_buffer& other) { insert(begin(), other.begin(), other.end()); } diff --git a/source/utility/util/containers/dense_set.h b/source/utility/util/containers/dense_set.h new file mode 100644 index 00000000..6903b095 --- /dev/null +++ b/source/utility/util/containers/dense_set.h @@ -0,0 +1,102 @@ +#pragma once +#include "../macros.h" + +namespace utility { + // TODO: add documentation + // TODO: explore alternate data structures which may result + // in better performance/cleaner code + + class dense_set { + public: + dense_set() = default; + dense_set(u64 capacity) : m_capacity(capacity) { + m_data.resize((capacity + 63) / 64, 0); + std::ranges::fill(m_data.begin(), m_data.end(), 0); + } + + void clear() { + std::fill(m_data.begin(), m_data.end(), 0); + } + + bool set_union(const dense_set& src) { + ASSERT(m_capacity >= src.m_capacity, "panic"); + u64 n = (src.m_capacity + 63) / 64; + u64 changes = 0; + + for (u64 i = 0; i < n; ++i) { + u64 old = m_data[i]; + u64 new_val = old | src.m_data[i]; + + m_data[i] = new_val; + changes |= (old ^ new_val); + } + + return static_cast(changes); + } + + void copy(const dense_set& src) { + ASSERT(m_capacity >= src.m_capacity, "panic"); + std::ranges::copy(src.m_data.begin(), src.m_data.end(), m_data.begin()); + } + + void put(u64 index) { + u64 slot = index / 64; + u64 pos = index % 64; + + if (slot >= m_data.size()) { + m_data.resize((index * 2 + 63) / 64, 0); + m_capacity = index * 2; + } + + m_data[slot] |= (1ull << pos); + } + + void remove(u64 index) { + u64 slot = index / 64; + u64 pos = index % 64; + + if (slot < m_data.size()) { + m_data[slot] &= ~(1ull << pos); + } + } + + bool get(u64 index) const { + u64 slot = index / 64; + u64 pos = index % 64; + + if (slot >= m_data.size()) { + return false; + } + + return m_data[slot] & (1ull << pos); + } + + u64 data(u64 index) const { + return m_data[index]; + } + + u64& data(u64 index) { + return m_data[index]; + } + + u64 capacity() const { + return m_capacity; + } + private: + std::vector m_data; + u64 m_capacity; + }; + + template + void foreach_set(const dense_set& ds, function func) { + for (u64 i = 0; i < (ds.capacity() + 63) / 64; ++i) { + u64 bits = ds.data(i); + + for (u64 it = i * 64; bits; bits >>= 1, ++it) { + if (bits & 1) { + func(it); + } + } + } + } +} // namespace utility \ No newline at end of file diff --git a/source/utility/containers/slice.h b/source/utility/util/containers/slice.h similarity index 92% rename from source/utility/containers/slice.h rename to source/utility/util/containers/slice.h index b833c637..9f172ce6 100644 --- a/source/utility/containers/slice.h +++ b/source/utility/util/containers/slice.h @@ -1,5 +1,5 @@ #pragma once -#include "utility/macros.h" +#include "../macros.h" namespace utility { template @@ -20,7 +20,7 @@ namespace utility { template slice(allocator& alloc, size_type count) : - m_data(static_cast(alloc.allocate_zero(sizeof(type) * count))), m_size(count) {} + m_data(static_cast(alloc.allocate_zero(sizeof(type)* count))), m_size(count) {} [[nodiscard]] auto get_data() const -> type* { return m_data; @@ -74,7 +74,7 @@ namespace utility { return m_data + m_size; } - [[nodiscard]] auto rend() -> reverse_iterator{ + [[nodiscard]] auto rend() -> reverse_iterator { return reverse_iterator(begin()); } @@ -88,7 +88,7 @@ namespace utility { } for(size_type i = 0; i < m_size; ++i) { - if(m_data[i] != other.m_data[i]) { + if (m_data[i] != other.m_data[i]) { return false; } } diff --git a/source/utility/util/containers/stack.h b/source/utility/util/containers/stack.h new file mode 100644 index 00000000..5c587788 --- /dev/null +++ b/source/utility/util/containers/stack.h @@ -0,0 +1,27 @@ +#pragma once +#include "contiguous_buffer.h" + +namespace utility { + template + class stack : public contiguous_buffer { + public: + stack() + : contiguous_buffer() {} + + stack(const std::initializer_list& initializer_list) + : contiguous_buffer(initializer_list) {} + + stack(const contiguous_buffer& container) + : contiguous_buffer(container) {} + + [[nodiscard]] constexpr auto pop_back() -> type& { + return this->m_data[--this->m_size]; + } + + constexpr void pop() { + --this->m_size; + } + private: + + }; +} // namespace utility diff --git a/source/utility/diagnostics.h b/source/utility/util/diagnostics.h similarity index 63% rename from source/utility/diagnostics.h rename to source/utility/util/diagnostics.h index b9d17c6b..fb3022a9 100644 --- a/source/utility/diagnostics.h +++ b/source/utility/util/diagnostics.h @@ -1,10 +1,19 @@ #pragma once -#include -#include - -#include "utility/types.h" +#include "types.h" namespace utility { + template + auto num_digits(type number) -> u8 { + u8 digits = 0; + + while (number) { + number /= 10; + digits++; + } + + return digits; + } + class console { public: static void print(const std::string& message) { @@ -64,18 +73,18 @@ namespace utility { // copy construct it again template - requires (!std::is_same_v, std::in_place_t> && std::is_constructible_v) + requires (!std::is_same_v, std::in_place_t> && std::is_constructible_v) constexpr explicit(!std::is_convertible_v) result(current value) noexcept(std::is_nothrow_constructible_v) : m_value(std::move(type(std::move(value)))) {} result(error failure) noexcept : m_value(std::move(failure)) {} - bool has_error() const { + auto has_error() const -> bool { return std::holds_alternative(m_value); } - bool has_value() const { + auto has_value() const -> bool { return !has_error(); } @@ -98,7 +107,7 @@ namespace utility { class result { public: result() noexcept = default; - result(error failure) noexcept : m_error_message(failure.get_message()) {} + result(const error& failure) noexcept : m_error_message(failure.get_message()) {} bool has_error() const { return !m_error_message.empty(); @@ -109,7 +118,7 @@ namespace utility { } auto get_error() const -> error { - return error(m_error_message); + return { m_error_message }; } private: std::string m_error_message; @@ -124,41 +133,41 @@ namespace utility { * \param __result utility::result to print */ #define PRINT_ERROR(__result) \ - do { \ - auto CONCATENATE(result, __LINE__) = __result; \ - if (CONCATENATE(result, __LINE__).has_error()) { \ - utility::console::println(CONCATENATE(result, __LINE__).get_error_message()); \ - } \ -} while (false) + do { \ + auto CONCATENATE(result, __LINE__) = __result; \ + if (CONCATENATE(result, __LINE__).has_error()) { \ + utility::console::println(CONCATENATE(result, __LINE__).get_error_message()); \ + } \ + } while (false) #define TRY_1(__result) \ -do { \ - auto CONCATENATE(result, __LINE__) = __result; \ - if (CONCATENATE(result, __LINE__).has_error()) { \ + do { \ + auto CONCATENATE(result, __LINE__) = __result; \ + if (CONCATENATE(result, __LINE__).has_error()) { \ + return utility::error((CONCATENATE(result, __LINE__)).get_error()); \ + } \ + } while(false) + +#define TRY_2(__success, __result) \ + auto CONCATENATE(result, __LINE__) = (__result); \ + if(CONCATENATE(result, __LINE__).has_error()) { \ return utility::error((CONCATENATE(result, __LINE__)).get_error()); \ - } \ -} while(false) - -#define TRY_2(__success, __result) \ -auto CONCATENATE(result, __LINE__) = (__result); \ -if(CONCATENATE(result, __LINE__).has_error()) { \ - return utility::error((CONCATENATE(result, __LINE__)).get_error()); \ -} \ -__success = CONCATENATE(result, __LINE__).get_value() + } \ + __success = CONCATENATE(result, __LINE__).get_value() #define EXPAND(x) x #define GET_MACRO(_1, _2, NAME, ...) NAME -/** - * \brief Rudimentary try macro, accepts a utility::result as the first argument. Immediately returns - * if the result contains an error and propagates the error further. Two variants: the first variant - * only returns, whilst the second one handles the 'value' contained in the result and stores it. - */ + /** + * \brief Rudimentary try macro, accepts a utility::result as the first argument. Immediately returns + * if the result contains an error and propagates the error further. Two variants: the first variant + * only returns, whilst the second one handles the 'value' contained in the result and stores it. + */ #define TRY(...) EXPAND(GET_MACRO(__VA_ARGS__, TRY_2, TRY_1)(__VA_ARGS__)) -/** - * \brief Used as a more streamlined way of handling success states of utility::result (instead - * of having to write stuff like utility::result() or {} we can just use this to hopefully - * reduce confusion related to this already relatively confusing system. - */ + /** + * \brief Used as a more streamlined way of handling success states of utility::result (instead + * of having to write stuff like utility::result() or {} we can just use this to hopefully + * reduce confusion related to this already relatively confusing system. + */ #define SUCCESS {} diff --git a/source/utility/filesystem/filepath.h b/source/utility/util/filesystem/filepath.h similarity index 98% rename from source/utility/filesystem/filepath.h rename to source/utility/util/filesystem/filepath.h index 1bd08f67..0015a3a4 100644 --- a/source/utility/filesystem/filepath.h +++ b/source/utility/util/filesystem/filepath.h @@ -1,5 +1,5 @@ #pragma once -#include "utility/types.h" +#include "../types.h" namespace utility::types { class filepath { diff --git a/source/utility/filesystem/file.h b/source/utility/util/filesystem/filesystem.h similarity index 90% rename from source/utility/filesystem/file.h rename to source/utility/util/filesystem/filesystem.h index 6b441018..089b1803 100644 --- a/source/utility/filesystem/file.h +++ b/source/utility/util/filesystem/filesystem.h @@ -1,7 +1,7 @@ #pragma once -#include "utility/containers/contiguous_container.h" -#include "utility/filesystem/filepath.h" -#include "utility/macros.h" +#include "../containers/contiguous_buffer.h" +#include "filepath.h" +#include "../macros.h" namespace utility { namespace fs { @@ -22,8 +22,8 @@ namespace utility { }; template - struct file> { - static result save(const filepath& path, const contiguous_container& value) { + struct file> { + static result save(const filepath& path, const contiguous_buffer& value) { std::ofstream file(path.get_path(), std::ios::binary); if (!file) { diff --git a/source/utility/util/handle.h b/source/utility/util/handle.h new file mode 100644 index 00000000..20ce8332 --- /dev/null +++ b/source/utility/util/handle.h @@ -0,0 +1,69 @@ +#pragma once +#include "types.h" + +namespace utility::types { + // NOTE: due to the lightweight nature of this class, atomic operations are NOT used + // and thus the container is not thread-safe. + + /** + * \brief Simple and lightweight non-owning pointer abstraction. + * \tparam type Type of the contained pointer + */ + template + class handle { + public: + handle() = default; + handle(type* ptr) : m_ptr(ptr) {} + + template + handle(other_type* other) : m_ptr(reinterpret_cast(other)) {} + + template + handle(handle other) : m_ptr(reinterpret_cast(other.get())) {} + + auto operator*() const -> type& { + return *m_ptr; + } + + auto operator->() const -> type* { + return m_ptr; + } + + [[nodiscard]] auto get() const -> type* { + return m_ptr; + } + + auto operator==(const handle& other) const -> bool { + return m_ptr == other.m_ptr; + } + + operator bool() const noexcept { + return m_ptr != nullptr; + } + protected: + type* m_ptr = nullptr; + }; +} // namespace utility + +template +struct std::hash> { + utility::u64 operator()(const utility::types::handle& h) const noexcept { + // hash the internal pointer + return std::hash{}(h.get()); + } +}; + +template +struct std::formatter> { + auto parse(format_parse_context& ctx) { + return ctx.begin(); + } + + auto format(const utility::types::handle& obj, format_context& ctx) { + if (obj) { + return format_to(ctx.out(), "{}", static_cast(obj.get())); + } + + return format_to(ctx.out(), "0"); + } +}; diff --git a/source/utility/macros.h b/source/utility/util/macros.h similarity index 64% rename from source/utility/macros.h rename to source/utility/util/macros.h index 69875337..a8000a4d 100644 --- a/source/utility/macros.h +++ b/source/utility/util/macros.h @@ -1,55 +1,54 @@ #pragma once - -#include "utility/diagnostics.h" +#include "diagnostics.h" #ifdef _WIN32 - #include +#include - #define DEBUG_BREAK() __debugbreak() - #define SYSTEM_WINDOWS +#define DEBUG_BREAK() __debugbreak() +#define SYSTEM_WINDOWS #elif __linux__ - #include +#include - #define DEBUG_BREAK() raise(SIGTRAP) - #define SYSTEM_LINUX +#define DEBUG_BREAK() raise(SIGTRAP) +#define SYSTEM_LINUX #else - #error "Unsupported platform!" +#error "Unsupported platform!" #endif #ifdef DEBUG #define ASSERT(__condition, __fmt, ...) \ - do { \ - if(!(__condition)) { \ - utility::console::printerr("ASSERTION FAILED: ({}:{}): ", __FILE__, __LINE__); \ - utility::console::printerr((__fmt), ##__VA_ARGS__); \ - utility::console::printerr("\n"); \ - utility::console::flush(); \ - DEBUG_BREAK(); \ - } \ - } while(false) + do { \ + if(!(__condition)) { \ + utility::console::printerr("ASSERTION FAILED: ({}:{}): ", __FILE__, __LINE__); \ + utility::console::printerr((__fmt), ##__VA_ARGS__); \ + utility::console::printerr("\n"); \ + utility::console::flush(); \ + DEBUG_BREAK(); \ + } \ + } while(false) #define NOT_IMPLEMENTED() \ - do { \ - utility::console::printerr("ASSERTION FAILED: ({}:{}): NOT IMPLEMENTED\n", __FILE__, __LINE__); \ - utility::console::errflush(); \ - DEBUG_BREAK(); \ - } while(false) + do { \ + utility::console::printerr("ASSERTION FAILED: ({}:{}): NOT IMPLEMENTED\n", __FILE__, __LINE__); \ + utility::console::errflush(); \ + DEBUG_BREAK(); \ + } while(false) #define PANIC( __fmt, ...) \ - do { \ - utility::console::printerr("ASSERTION FAILED: ({}:{}): ", __FILE__, __LINE__); \ - utility::console::printerr((__fmt), ##__VA_ARGS__); \ - utility::console::printerr("\n"); \ - utility::console::errflush(); \ - DEBUG_BREAK(); \ - } while(false) + do { \ + utility::console::printerr("ASSERTION FAILED: ({}:{}): ", __FILE__, __LINE__); \ + utility::console::printerr((__fmt), ##__VA_ARGS__); \ + utility::console::printerr("\n"); \ + utility::console::errflush(); \ + DEBUG_BREAK(); \ + } while(false) #else #define ASSERT(__condition, __fmt, ...) #define NOT_IMPLEMENTED() #define PANIC(__fmt, ...) #endif -#define SUPPRESS_C4100(_statement) (void)_statement +#define SUPPRESS_C4100(__statement) (void)__statement /** * \brief Declares the given \a __enum to have various binary operands useful @@ -78,11 +77,3 @@ inline constexpr std::underlying_type_t<__enum> operator + (std::underlying_type inline constexpr std::underlying_type_t<__enum> operator ^ (__enum e, std::underlying_type_t<__enum> value) { return (std::underlying_type_t<__enum>)e ^ value; } \ inline constexpr std::underlying_type_t<__enum> operator ^ (std::underlying_type_t<__enum> value, __enum e) { return value ^ (std::underlying_type_t<__enum>)e; } \ inline constexpr bool operator == (std::underlying_type_t<__enum> value, __enum e) { return value == (std::underlying_type_t<__enum>)e; } \ - -#if defined(_MSC_VER) -#define aligned_malloc _aligned_malloc -#define aligned_free _aligned_free -#else -#define aligned_malloc std::aligned_alloc -#define aligned_free std::free -#endif \ No newline at end of file diff --git a/source/utility/util/memory.h b/source/utility/util/memory.h new file mode 100644 index 00000000..ad3e8898 --- /dev/null +++ b/source/utility/util/memory.h @@ -0,0 +1,97 @@ +#pragma once +#include "diagnostics.h" + +namespace utility { + struct byte { + byte() = default; + byte(u8 value) : value(value) {} + + [[nodiscard]] auto to_hex() const -> std::string { + std::ostringstream oss; + oss << std::hex << std::setw(2) << std::setfill('0') << static_cast(value); + return oss.str(); + } + + auto operator |=(const byte& other) -> byte { + value |= other.value; + return *this; + } + + auto operator &=(const byte& other) -> byte { + value &= other.value; + return *this; + } + + auto operator <<(i32 distance) const -> byte { + return byte(value << distance); + } + + auto operator >>(i32 distance) const -> byte { + return byte(value >> distance); + } + + auto operator +(const byte& other) const->byte { + return value + other.value; + } + + operator u8() const { + return value; + } + + u8 value; + }; + + /** + * \brief Aligns the specified \a value into blocks of the specified width. + * \param value Value to align + * \param alignment Alignment to use + * \return Aligned \a value. + */ + [[nodiscard]] inline auto align(u64 value, u64 alignment) -> u64 { + return value + (alignment - (value % alignment)) % alignment; + } + + [[nodiscard]] inline auto sign_extend(u64 src, u64 src_bits, u64 dst_bits) -> u64 { + const u64 sign_bit = (src >> (src_bits - 1)) & 1; + + const u64 mask_dst_bits = ~UINT64_C(0) >> (64 - dst_bits); + const u64 mask_src_bits = ~UINT64_C(0) >> (64 - src_bits); + const u64 mask = mask_dst_bits & ~mask_src_bits; + + // initialize dst by zeroing out the bits from src_bits to dst_bits + const u64 dst = src & ~mask; + + // perform the actual sign extension + return dst | (sign_bit ? mask : 0); + } + + [[nodiscard]] auto inline fits_into_8_bits(u64 value) -> bool { + return static_cast(value) <= std::numeric_limits::max(); + } + + [[nodiscard]] auto inline fits_into_32_bits(u64 value) -> bool { + return static_cast(value) <= std::numeric_limits::max(); + } + + [[nodiscard]] auto inline pop_count(u32 value) -> u8 { + return static_cast(std::bitset<32>(value).count()); + } + + [[nodiscard]] auto inline ffs(i32 value) -> u8 { + if (value == 0) { + return 0; + } + + u8 pos = 1; + while (!(value & 1)) { + value >>= 1; + pos++; + } + + return pos; + } + + [[nodiscard]] auto inline is_power_of_two(u64 value) -> bool { + return (value & (value - 1)) == 0; + } +} // namespace utility diff --git a/source/utility/containers/property.h b/source/utility/util/property.h similarity index 92% rename from source/utility/containers/property.h rename to source/utility/util/property.h index 85deeb75..b14db829 100644 --- a/source/utility/containers/property.h +++ b/source/utility/util/property.h @@ -1,5 +1,5 @@ #pragma once -#include "utility/containers/handle.h" +#include "handle.h" namespace utility { struct empty_property {}; diff --git a/source/utility/containers/range.h b/source/utility/util/range.h similarity index 85% rename from source/utility/containers/range.h rename to source/utility/util/range.h index 21945e15..c67754e5 100644 --- a/source/utility/containers/range.h +++ b/source/utility/util/range.h @@ -1,5 +1,5 @@ #pragma once -#include "utility/types.h" +#include "types.h" namespace utility { template @@ -23,7 +23,7 @@ namespace utility { }; template - ptr_diff range_intersect(range& a, range& b) { + auto range_intersect(range& a, range& b) -> ptr_diff { if (b.start <= a.end && a.start <= b.end) { return static_cast(a.start > b.start ? a.start : b.start); } diff --git a/source/utility/util/shell.h b/source/utility/util/shell.h new file mode 100644 index 00000000..0d01a6ca --- /dev/null +++ b/source/utility/util/shell.h @@ -0,0 +1,50 @@ +#pragma once +#include "macros.h" + +namespace utility { + /** + * \brief Basic shell abstraction, used for invoking commands on the local system. + */ + struct shell { + /** + * \brief Runs the specified \b command in the local shell. + * \param command Command to run + * \return 0 if all operations succeeded, non-zero otherwise. + */ + static auto execute(const std::string& command) -> i32 { +#ifdef SYSTEM_WINDOWS + const std::string windows_command = "cmd /c " + command; + return system(windows_command.c_str()); +#elif defined SYSTEM_LINUX + i32 status = system(command.c_str()); + if (WIFEXITED(status)) { + return WEXITSTATUS(status); + } + else { + // abnormal termination + return -1; + } +#else + PANIC("unsupported platform detected"); +#endif + } + + /** + * \brief Opens the specified \b link in the local browser. + * \param link Link to open + * \return 0 if all operations succeeded, non-zero otherwise. + */ + static auto open_link(const std::string& link) -> i32 { + std::string command; +#ifdef SYSTEM_WINDOWS + command = "start " + link; +#elif defined SYSTEM_LINUX + command = "xdg-open " + link; +#else + PANIC("unsupported platform detected"); +#endif + + return execute(command); + } + }; +} // namespace utility diff --git a/source/utility/string_helper.h b/source/utility/util/string/const_string.h similarity index 56% rename from source/utility/string_helper.h rename to source/utility/util/string/const_string.h index 89609a5e..b9396110 100644 --- a/source/utility/string_helper.h +++ b/source/utility/util/string/const_string.h @@ -1,53 +1,51 @@ #pragma once -#include "utility/types.h" - -namespace utility::detail { - /** - * \brief Chooses a string based on \a count (plural vs. singular). - * \param count Count - * \param singular Singular string - * \param plural Plural strings - * \return Singular/plural string based on the \a count. - */ - auto format_ending(u64 count, const std::string& singular, const std::string& plural) -> std::string; - - /** - * \brief Creates a string of length \a end with caret characters ('^') starting at \a start. - * \param str String to underline with carets - * \param start Start index - * \param end End index - * \return String containing a caret underline. - */ - auto create_caret_underline(const std::string& str, u64 start, u64 end) -> std::string; - - /** - * \brief Checks if the char \a c is part of the hexadecimal character set (0123456789ABCDEF). - * \param c Character to check - * \return True if the character is part of the hexadecimal character set. - */ - bool is_hex(char c); - - /** - * \brief Checks if the char \a c is part of the binary character set (01). - * \param c Character to check - * \return True if the character is part of the binary character set. - */ - bool is_bin(char c); +#include "../types.h" +namespace utility { /** * \brief Creates a backslash-escaped version of the \a input string. * \param input String to escape. * \return Escaped version of the given string. */ - auto escape_string(const std::string& input) -> std::string; - - auto remove_leading_spaces(const std::string& str) -> std::pair; - - void string_replace(std::string& str, const std::string& from, const std::string& to); + inline auto escape_string(const std::string& input) -> std::string { + // TODO: update to support various hexadecimal and binary strings + std::string output; + + for (const char ch : input) { + if (ch == '\\' || ch == '\'' || ch == '\"' || ch == '\a' || ch == '\b' || ch == '\f' || ch == '\n' || ch == '\r' || ch == '\t' || ch == '\v' || ch == '\x1b') { + output.push_back('\\'); + switch (ch) { + case '\\': output.push_back('\\'); break; + case '\'': output.push_back('\''); break; + case '\"': output.push_back('\"'); break; + case '\a': output.push_back('a'); break; + case '\b': output.push_back('b'); break; + case '\f': output.push_back('f'); break; + case '\n': output.push_back('n'); break; + case '\r': output.push_back('r'); break; + case '\t': output.push_back('t'); break; + case '\v': output.push_back('v'); break; + case '\x1b': + output.append("x1b"); + break; + } + } + else { + output.push_back(ch); + } + } + return output; + } - auto remove_first_line(const std::string& string) -> std::string; + inline auto is_only_char(const std::string& s, char c) -> bool { + for (const char ch : s) { + if (ch != c) { + return false; + } + } - auto is_only_char(const std::string& s, char c) -> bool; + return true; + } template auto unsigned_from_string(const std::string& string, bool& overflow) -> type { @@ -56,13 +54,13 @@ namespace utility::detail { type result = 0; u64 start_index = 0; - if(!string.empty() && string[0] == '-') { + if (!string.empty() && string[0] == '-') { is_negative = true; overflow = true; start_index = 1; } - for(u64 i = start_index; i < string.length(); ++i) { + for (u64 i = start_index; i < string.length(); ++i) { const char ch = string[i]; if (!std::isdigit(ch)) { @@ -73,7 +71,7 @@ namespace utility::detail { i32 digit = ch - '0'; // check for overflow - if(result > (std::numeric_limits::max() - digit) / 10) { + if (result > (std::numeric_limits::max() - digit) / 10) { overflow = true; result = (result * 10 + digit) & std::numeric_limits::max(); } @@ -83,7 +81,7 @@ namespace utility::detail { } // handle underflow by converting to max value for negative inputs - if(is_negative) { + if (is_negative) { return std::numeric_limits::max() - result + 1; } @@ -103,7 +101,7 @@ namespace utility::detail { static_assert( std::is_integral_v || std::is_floating_point_v, "'type' must be integral or floating point" - ); + ); overflowed = false; @@ -129,4 +127,4 @@ namespace utility::detail { } } } -} // namespace sigma +} // namespace utility diff --git a/source/utility/util/string/string_accessor.h b/source/utility/util/string/string_accessor.h new file mode 100644 index 00000000..9abca507 --- /dev/null +++ b/source/utility/util/string/string_accessor.h @@ -0,0 +1,90 @@ +#pragma once +#include "../macros.h" + +namespace utility { + /** + * \brief Utility string container class with support for caret movements. + */ + class string_accessor { + public: + string_accessor() = default; + + /** + * \brief Constructs the string accessor by filling it with the specified \a string. + * \param string String to use as the base of the string accessor + */ + string_accessor(const std::string& string) : m_string(string) {} + + /** + * \brief Increments the caret location. + */ + void advance() { + m_position++; + } + + /** + * \brief Decrements the caret location. + */ + void retreat() { + m_position--; + } + + auto peek_next_char() const -> char { + ASSERT(m_position + 1 <= m_string.size(), "accessor out of range! (peek_next_char)"); + return m_string[m_position + 1]; + } + + /** + * \brief Retrieves the character at the current caret location, if we are out of bounds an assertion is triggered. + * \returns Character at the current caret location + */ + [[nodiscard]] auto get() const -> char { + // check if we are inside of our strings' bounds + if (m_position <= m_string.size()) { + return m_string[m_position]; + } + + // out-of-bounds access + return EOF; + } + + /** + * \brief Retrieves the character at the current caret location and increments the caret location, if we are out of bounds an assertion is triggered + * \returns Character at the current caret location + */ + [[nodiscard]] auto get_advance() -> char { + const char temp = get(); + advance(); + return temp; + } + + /** + * \brief Checks whether the current caret location is in/out of bounds of the contained string. + * \return True if the caret is out of bounds, otherwise False + */ + [[nodiscard]] auto end() const -> bool { + return m_position > m_string.size(); + } + + [[nodiscard]] auto get_data() const -> const std::string& { + return m_string; + } + + [[nodiscard]] auto get_data() -> std::string& { + return m_string; + } + + [[nodiscard]] auto get_position() const->u64 { + return m_position; + } + + void set_position(u64 position) { + // check if we are inside of our strings' bounds + ASSERT(position <= m_string.size(), "accessor out of range! (set)"); + m_position = position; + } + private: + std::string m_string; // contained string + u64 m_position = 0; // current caret location + }; +} // namespace utility diff --git a/source/utility/util/string/string_table.h b/source/utility/util/string/string_table.h new file mode 100644 index 00000000..195e8b4c --- /dev/null +++ b/source/utility/util/string/string_table.h @@ -0,0 +1,66 @@ +#pragma once +#include "../macros.h" + +namespace utility { + struct string_table_key { + string_table_key() : m_value(0) {} + string_table_key(const std::string& string) : m_value(std::hash{}(string)) {} + + auto operator==(string_table_key other) const -> bool { + return m_value == other.m_value; + } + + auto operator<(string_table_key other) const -> bool { + return m_value < other.m_value; + } + + auto get_value() const -> u64 { + return m_value; + } + + auto is_valid() const -> bool { + return m_value != 0; + } + private: + u64 m_value; // already hashed + }; +} // namespace utility + +template <> +struct std::hash { + auto operator()(const utility::string_table_key& k) const noexcept -> utility::u64 { + // since we've already hashed the string in the constructor we can use the value + return k.get_value(); + } +}; + +namespace utility { + class string_table { + public: + string_table() = default; + + bool contains(string_table_key key) const { + return m_key_to_string.contains(key); + } + + auto insert(const std::string& string) -> string_table_key { + const string_table_key new_key(string); + + const auto it = m_key_to_string.find(new_key); + if (it != m_key_to_string.end()) { + // the key is already contained in the table + return new_key; + } + + m_key_to_string[new_key] = string; + return new_key; + } + + auto get(string_table_key key) const -> const std::string& { + ASSERT(key.get_value() != 0, "invalid symbol key"); + return m_key_to_string.at(key); + } + private: + std::unordered_map m_key_to_string; + }; +} // namespace utility diff --git a/source/utility/util/timer.h b/source/utility/util/timer.h new file mode 100644 index 00000000..cb4bc03b --- /dev/null +++ b/source/utility/util/timer.h @@ -0,0 +1,22 @@ +#pragma once +#include "macros.h" + +namespace utility { + class timer { + public: + void start(); + + template + [[nodiscard]] auto elapsed() const -> f64 { + const auto now = std::chrono::high_resolution_clock::now(); + const auto diff = now - m_start; + return std::chrono::duration_cast(diff).count(); + } + + [[nodiscard]] f64 elapsed_seconds() const { + return elapsed>(); + } + private: + std::chrono::high_resolution_clock::time_point m_start; + }; +} // namespace utility diff --git a/source/utility/util/types.h b/source/utility/util/types.h new file mode 100644 index 00000000..53543c0e --- /dev/null +++ b/source/utility/util/types.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// platform specific +#ifdef _WIN32 +#define NOMINMAX +#include +#include +#else +#include +#include +#include +#endif + +namespace utility { + namespace types { + // signed integers + using i8 = int8_t; + using i16 = int16_t; + using i32 = int32_t; + using i64 = int64_t; + + // unsigned integers + using u8 = uint8_t; + using u16 = uint16_t; + using u32 = uint32_t; + using u64 = uint64_t; + + // floating point + using f32 = float; + using f64 = double; + + using ptr_diff = ptrdiff_t; + + template + using s_ptr = std::shared_ptr; + + template + using u_ptr = std::unique_ptr; + } // namespace utility::types + + using namespace types; +} // namespace utility