aboutsummaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/acpi/CMakeLists.txt67
-rw-r--r--libs/acpi/acpi/acpi.hpp12
-rw-r--r--libs/acpi/acpi/common/basic_table.hpp35
-rw-r--r--libs/acpi/acpi/common/checksum.cpp (renamed from libs/acpi/acpi/checksum.cpp)2
-rw-r--r--libs/acpi/acpi/common/checksum.hpp (renamed from libs/acpi/acpi/checksum.hpp)0
-rw-r--r--libs/acpi/acpi/common/table_header.cpp130
-rw-r--r--libs/acpi/acpi/common/table_header.hpp56
-rw-r--r--libs/acpi/acpi/common/table_header.test.cpp58
-rw-r--r--libs/acpi/acpi/common/table_signature.hpp17
-rw-r--r--libs/acpi/acpi/common/table_type.hpp (renamed from libs/acpi/acpi/table_type.hpp)4
-rw-r--r--libs/acpi/acpi/common/vla_table.hpp115
-rw-r--r--libs/acpi/acpi/data/madt.cpp112
-rw-r--r--libs/acpi/acpi/data/madt.hpp117
-rw-r--r--libs/acpi/acpi/data/madt.test.cpp55
-rw-r--r--libs/acpi/acpi/data/rsdt.cpp38
-rw-r--r--libs/acpi/acpi/data/rsdt.hpp42
-rw-r--r--libs/acpi/acpi/data/rsdt.test.cpp46
-rw-r--r--libs/acpi/acpi/data/xsdt.cpp38
-rw-r--r--libs/acpi/acpi/data/xsdt.hpp42
-rw-r--r--libs/acpi/acpi/data/xsdt.test.cpp46
-rw-r--r--libs/acpi/acpi/madt.cpp68
-rw-r--r--libs/acpi/acpi/madt.hpp92
-rw-r--r--libs/acpi/acpi/pointers.cpp6
-rw-r--r--libs/acpi/acpi/pointers.test.cpp29
-rw-r--r--libs/acpi/acpi/sdt.cpp51
-rw-r--r--libs/acpi/acpi/sdt.hpp119
-rw-r--r--libs/acpi/test_data/basic_madt.asl29
-rw-r--r--libs/acpi/test_data/basic_rsdp.asl16
-rw-r--r--libs/acpi/test_data/basic_rsdt.asl26
-rw-r--r--libs/acpi/test_data/basic_xsdt.asl26
-rw-r--r--libs/acpi/test_data/table_header.asl9
-rw-r--r--libs/acpi/test_data/tables.S17
-rw-r--r--libs/acpi/test_data/tables.hpp28
-rw-r--r--libs/kstd/CMakeLists.txt2
-rw-r--r--libs/kstd/include/kstd/bits/format/context.hpp17
-rw-r--r--libs/kstd/include/kstd/bits/format/formatter/bool.hpp4
-rw-r--r--libs/kstd/include/kstd/bits/format/formatter/char.hpp94
-rw-r--r--libs/kstd/include/kstd/bits/format/formatter/integral.hpp4
-rw-r--r--libs/kstd/include/kstd/bits/format/output_buffer.hpp32
-rw-r--r--libs/kstd/include/kstd/bits/format/specifiers.hpp6
-rw-r--r--libs/kstd/include/kstd/bits/format/vformat.hpp99
-rw-r--r--libs/kstd/include/kstd/flat_map27
-rw-r--r--libs/kstd/include/kstd/format3
-rw-r--r--libs/kstd/include/kstd/string20
-rw-r--r--libs/kstd/include/kstd/vector41
-rw-r--r--libs/kstd/src/vformat.cpp209
-rw-r--r--libs/kstd/tests/src/format.cpp116
-rw-r--r--libs/kstd/tests/src/vector.cpp45
48 files changed, 1909 insertions, 358 deletions
diff --git a/libs/acpi/CMakeLists.txt b/libs/acpi/CMakeLists.txt
index c6e63b9..55d5b54 100644
--- a/libs/acpi/CMakeLists.txt
+++ b/libs/acpi/CMakeLists.txt
@@ -1,3 +1,11 @@
+cmake_minimum_required(VERSION "3.27.0")
+
+project("acpi"
+ DESCRIPTION "An ACPI parsing library"
+ VERSION "0.0.1"
+ LANGUAGES ASM CXX
+)
+
add_library("acpi" STATIC)
add_library("libs::acpi" ALIAS "acpi")
@@ -11,10 +19,12 @@ file(GLOB_RECURSE ACPI_HEADERS
)
target_sources("acpi" PRIVATE
- "acpi/checksum.cpp"
- "acpi/madt.cpp"
+ "acpi/common/checksum.cpp"
+ "acpi/common/table_header.cpp"
+ "acpi/data/madt.cpp"
+ "acpi/data/rsdt.cpp"
+ "acpi/data/xsdt.cpp"
"acpi/pointers.cpp"
- "acpi/sdt.cpp"
)
target_sources("acpi" PUBLIC
@@ -27,3 +37,54 @@ target_sources("acpi" PUBLIC
target_link_libraries("acpi" PUBLIC
"libs::kstd"
)
+
+if(NOT CMAKE_CROSSCOMPILING)
+ find_program(IASL_EXE NAMES "iasl" REQUIRED)
+
+ set(TEST_TABLES
+ "basic_madt"
+ "basic_rsdt"
+ "basic_rsdp"
+ "basic_xsdt"
+ "table_header"
+ )
+
+ foreach(TABLE IN LISTS TEST_TABLES)
+ add_custom_command(OUTPUT "test_data/${TABLE}.aml"
+ COMMAND "${IASL_EXE}" -p "test_data/${TABLE}.aml" "${CMAKE_CURRENT_SOURCE_DIR}/test_data/${TABLE}.asl"
+ DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/test_data/${TABLE}.asl"
+ COMMENT "Compiling test_data/${TABLE}.asl"
+ VERBATIM
+ )
+ list(APPEND GENERATED_TABLE_BLOBS "${CMAKE_CURRENT_BINARY_DIR}/test_data/${TABLE}.aml")
+ endforeach()
+
+ set_source_files_properties("test_data/tables.S" PROPERTIES OBJECT_DEPENDS "${GENERATED_TABLE_BLOBS}")
+
+ add_executable("acpi_tests"
+ "acpi/common/table_header.test.cpp"
+ "acpi/data/madt.test.cpp"
+ "acpi/data/rsdt.test.cpp"
+ "acpi/data/xsdt.test.cpp"
+ "acpi/pointers.test.cpp"
+
+ "test_data/tables.S"
+ )
+
+ target_include_directories("acpi_tests" PRIVATE
+ "${CMAKE_CURRENT_BINARY_DIR}/test_data"
+ )
+
+ target_link_libraries("acpi_tests" PRIVATE
+ "Catch2::Catch2WithMain"
+ "libs::acpi"
+ )
+
+ set_target_properties("acpi_tests" PROPERTIES
+ C_CLANG_TIDY ""
+ CXX_CLANG_TIDY ""
+ EXCLUDE_FROM_ALL NO
+ )
+
+ catch_discover_tests("acpi_tests")
+endif()
diff --git a/libs/acpi/acpi/acpi.hpp b/libs/acpi/acpi/acpi.hpp
index fb358cc..0da44a8 100644
--- a/libs/acpi/acpi/acpi.hpp
+++ b/libs/acpi/acpi/acpi.hpp
@@ -1,10 +1,12 @@
#ifndef ACPI_ACPI_HPP
#define ACPI_ACPI_HPP
-#include <acpi/checksum.hpp> // IWYU pragma: export
-#include <acpi/madt.hpp> // IWYU pragma: export
-#include <acpi/pointers.hpp> // IWYU pragma: export
-#include <acpi/sdt.hpp> // IWYU pragma: export
-#include <acpi/table_type.hpp> // IWYU pragma: export
+#include <acpi/common/checksum.hpp> // IWYU pragma: export
+#include <acpi/common/table_signature.hpp> // IWYU pragma: export
+#include <acpi/common/table_type.hpp> // IWYU pragma: export
+#include <acpi/data/madt.hpp> // IWYU pragma: export
+#include <acpi/data/rsdt.hpp> // IWYU pragma: export
+#include <acpi/data/xsdt.hpp> // IWYU pragma: export
+#include <acpi/pointers.hpp> // IWYU pragma: export
#endif
diff --git a/libs/acpi/acpi/common/basic_table.hpp b/libs/acpi/acpi/common/basic_table.hpp
new file mode 100644
index 0000000..33f23d5
--- /dev/null
+++ b/libs/acpi/acpi/common/basic_table.hpp
@@ -0,0 +1,35 @@
+#ifndef ACPI_COMMON_BASIC_TABLE_HPP
+#define ACPI_COMMON_BASIC_TABLE_HPP
+
+#include <acpi/common/checksum.hpp>
+#include <acpi/common/table_header.hpp>
+#include <acpi/common/table_signature.hpp>
+
+#include <cstddef>
+#include <span>
+
+namespace acpi
+{
+
+ template<typename TableType>
+ struct basic_table : table_header
+ {
+ [[nodiscard]] auto validate_checksum() const noexcept -> bool
+ {
+ return acpi::validate_checksum(as_span());
+ }
+
+ [[nodiscard]] auto as_span() const noexcept -> std::span<std::byte const>
+ {
+ return {reinterpret_cast<std::byte const *>(this), length().value};
+ }
+
+ [[nodiscard]] auto validate() const noexcept -> bool
+ {
+ return signature() == table_signature_v<TableType> && validate_checksum();
+ }
+ };
+
+} // namespace acpi
+
+#endif \ No newline at end of file
diff --git a/libs/acpi/acpi/checksum.cpp b/libs/acpi/acpi/common/checksum.cpp
index 56275c8..09425dd 100644
--- a/libs/acpi/acpi/checksum.cpp
+++ b/libs/acpi/acpi/common/checksum.cpp
@@ -1,4 +1,4 @@
-#include <acpi/checksum.hpp>
+#include <acpi/common/checksum.hpp>
#include <algorithm>
#include <cstddef>
diff --git a/libs/acpi/acpi/checksum.hpp b/libs/acpi/acpi/common/checksum.hpp
index a92c242..a92c242 100644
--- a/libs/acpi/acpi/checksum.hpp
+++ b/libs/acpi/acpi/common/checksum.hpp
diff --git a/libs/acpi/acpi/common/table_header.cpp b/libs/acpi/acpi/common/table_header.cpp
new file mode 100644
index 0000000..c69ff5d
--- /dev/null
+++ b/libs/acpi/acpi/common/table_header.cpp
@@ -0,0 +1,130 @@
+#include <kstd/cstring>
+#include <kstd/units>
+
+#include <acpi/common/table_header.hpp>
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <span>
+#include <string_view>
+
+namespace acpi
+{
+
+ struct common_table_header_data
+ {
+ std::array<char, 4> signature;
+ std::uint32_t length;
+ std::uint8_t revision;
+ std::uint8_t checksum;
+ std::array<char, 6> oem_id;
+ std::array<char, 8> oem_table_id;
+ std::uint32_t oem_revision;
+ std::array<char, 4> creator_id;
+ std::uint32_t creator_revision;
+ };
+
+ static_assert(sizeof(common_table_header_data) == common_table_header_size);
+
+ auto table_header::creator_revision() const noexcept -> decltype(common_table_header_data::creator_revision)
+ {
+ using type = decltype(common_table_header_data::creator_revision);
+
+ constexpr auto size = sizeof(type);
+ constexpr auto offset = offsetof(common_table_header_data, creator_revision);
+
+ auto const data = std::span<std::byte const, size>{m_data.data() + offset, size};
+ auto value = type{};
+
+ kstd::libc::memcpy(&value, data.data(), size);
+
+ return value;
+ }
+
+ auto table_header::creator_id() const noexcept -> std::string_view
+ {
+ constexpr auto size = sizeof(common_table_header_data::creator_id);
+ constexpr auto offset = offsetof(common_table_header_data, creator_id);
+
+ auto const base = reinterpret_cast<char const *>(m_data.data());
+
+ return std::string_view{base + offset, size};
+ }
+
+ auto table_header::length() const noexcept -> kstd::units::bytes
+ {
+ using type = decltype(common_table_header_data::length);
+
+ constexpr auto size = sizeof(type);
+ constexpr auto offset = offsetof(common_table_header_data, length);
+
+ auto const data = std::span<std::byte const, size>{m_data.data() + offset, size};
+ auto raw_value = type{};
+
+ kstd::libc::memcpy(&raw_value, data.data(), size);
+
+ return kstd::units::bytes{raw_value};
+ }
+
+ auto table_header::oem_id() const noexcept -> std::string_view
+ {
+ constexpr auto size = sizeof(common_table_header_data::oem_id);
+ constexpr auto offset = offsetof(common_table_header_data, oem_id);
+
+ auto const base = reinterpret_cast<char const *>(m_data.data());
+
+ return std::string_view{base + offset, size};
+ }
+
+ auto table_header::oem_table_id() const noexcept -> std::string_view
+ {
+ constexpr auto size = sizeof(common_table_header_data::oem_table_id);
+ constexpr auto offset = offsetof(common_table_header_data, oem_table_id);
+
+ auto const base = reinterpret_cast<char const *>(m_data.data());
+
+ return std::string_view{base + offset, size};
+ }
+
+ auto table_header::oem_revision() const noexcept -> decltype(common_table_header_data::oem_revision)
+ {
+ using type = decltype(common_table_header_data::oem_revision);
+
+ constexpr auto size = sizeof(type);
+ constexpr auto offset = offsetof(common_table_header_data, oem_revision);
+
+ auto const data = std::span<std::byte const, size>{m_data.data() + offset, size};
+ auto value = type{};
+
+ kstd::libc::memcpy(&value, data.data(), size);
+
+ return value;
+ }
+
+ auto table_header::revision() const noexcept -> decltype(common_table_header_data::revision)
+ {
+ using type = decltype(common_table_header_data::revision);
+
+ constexpr auto size = sizeof(type);
+ constexpr auto offset = offsetof(common_table_header_data, revision);
+
+ auto const data = std::span<std::byte const, size>{m_data.data() + offset, size};
+ auto value = type{};
+
+ kstd::libc::memcpy(&value, data.data(), size);
+
+ return value;
+ }
+
+ auto table_header::signature() const noexcept -> std::string_view
+ {
+ constexpr auto size = sizeof(common_table_header_data::signature);
+ constexpr auto offset = offsetof(common_table_header_data, signature);
+
+ auto const base = reinterpret_cast<char const *>(m_data.data());
+
+ return std::string_view{base + offset, size};
+ }
+
+} // namespace acpi \ No newline at end of file
diff --git a/libs/acpi/acpi/common/table_header.hpp b/libs/acpi/acpi/common/table_header.hpp
new file mode 100644
index 0000000..471fed8
--- /dev/null
+++ b/libs/acpi/acpi/common/table_header.hpp
@@ -0,0 +1,56 @@
+#ifndef ACPI_COMMON_TABLE_HEADER_HPP
+#define ACPI_COMMON_TABLE_HEADER_HPP
+
+// IWYU pragma: private, include <acpi/acpi.hpp>
+
+#include <kstd/units>
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <string_view>
+
+namespace acpi
+{
+
+ //! The size of the common table header as defined in the ACPI specification.
+ constexpr auto common_table_header_size = 36;
+
+ //! The common header for all ACPI tables, except the FACS.
+ //!
+ //! Multibyte fields in the header are not guaranteed to be naturally aligned by the firmware. Therefore, no direct
+ //! access to multibyte fields is provided. Instead, the provided member functions handle alignment of the multibyte
+ //! values and thus provide a safe interface to the header fields.
+ struct table_header
+ {
+ //! Get the revision of the utility used to create this table.
+ [[nodiscard]] auto creator_revision() const noexcept -> std::uint32_t;
+
+ //! Get the vendor ID of the utility used to create this table.
+ [[nodiscard]] auto creator_id() const noexcept -> std::string_view;
+
+ //! Get the length of the entire table, including this header.
+ [[nodiscard]] auto length() const noexcept -> kstd::units::bytes;
+
+ //! Get the ID of the OEM.
+ [[nodiscard]] auto oem_id() const noexcept -> std::string_view;
+
+ //! Get the OEMs revision number of this table.
+ [[nodiscard]] auto oem_revision() const noexcept -> std::uint32_t;
+
+ //! Get the OEMs ID of this table.
+ [[nodiscard]] auto oem_table_id() const noexcept -> std::string_view;
+
+ //! Get the revision number of the structure of this table.
+ [[nodiscard]] auto revision() const noexcept -> std::uint8_t;
+
+ //! Get the signature of this table.
+ [[nodiscard]] auto signature() const noexcept -> std::string_view;
+
+ private:
+ std::array<std::byte, common_table_header_size> m_data;
+ };
+
+} // namespace acpi
+
+#endif \ No newline at end of file
diff --git a/libs/acpi/acpi/common/table_header.test.cpp b/libs/acpi/acpi/common/table_header.test.cpp
new file mode 100644
index 0000000..d6976b3
--- /dev/null
+++ b/libs/acpi/acpi/common/table_header.test.cpp
@@ -0,0 +1,58 @@
+#include <kstd/units>
+
+#include <acpi/common/table_header.hpp>
+#include <catch2/catch_test_macros.hpp>
+#include <test_data/tables.hpp>
+
+SCENARIO("Common table header parsing", "[common_table_header]")
+{
+ GIVEN("A valid compiled table header")
+ {
+ auto data = acpi::test_data::tables::table_header();
+
+ WHEN("parsing the header")
+ {
+ auto header = reinterpret_cast<acpi::table_header const *>(data.data());
+
+ THEN("the signature is correct")
+ {
+ REQUIRE(header->signature() == "TEST");
+ }
+
+ THEN("the revision is correct")
+ {
+ REQUIRE(header->revision() == 1);
+ }
+
+ THEN("the length is correct")
+ {
+ REQUIRE(header->length() == kstd::type_size<acpi::table_header>);
+ }
+
+ THEN("the oem id is correct")
+ {
+ REQUIRE(header->oem_id() == "FEMO ");
+ }
+
+ THEN("the oem table id is correct")
+ {
+ REQUIRE(header->oem_table_id() == "HDRTEST ");
+ }
+
+ THEN("the oem revision is correct")
+ {
+ REQUIRE(header->oem_revision() == 1);
+ }
+
+ THEN("the creator id is correct")
+ {
+ REQUIRE(header->creator_id() == "INTL");
+ }
+
+ THEN("the creator revision is non-zero")
+ {
+ REQUIRE(header->creator_revision() != 0);
+ }
+ }
+ }
+}
diff --git a/libs/acpi/acpi/common/table_signature.hpp b/libs/acpi/acpi/common/table_signature.hpp
new file mode 100644
index 0000000..f0c2d7a
--- /dev/null
+++ b/libs/acpi/acpi/common/table_signature.hpp
@@ -0,0 +1,17 @@
+#ifndef ACPI_COMMON_TABLE_SIGNATURE_HPP
+#define ACPI_COMMON_TABLE_SIGNATURE_HPP
+
+// IWYU pragma: private, include <acpi/acpi.hpp>
+
+namespace acpi
+{
+
+ template<typename TableType>
+ struct table_signature;
+
+ template<typename TableType>
+ constexpr auto table_signature_v = table_signature<TableType>::value;
+
+} // namespace acpi
+
+#endif
diff --git a/libs/acpi/acpi/table_type.hpp b/libs/acpi/acpi/common/table_type.hpp
index 7fdd7e3..bc427c7 100644
--- a/libs/acpi/acpi/table_type.hpp
+++ b/libs/acpi/acpi/common/table_type.hpp
@@ -1,5 +1,5 @@
-#ifndef ACPI_TABLE_TYPE_HPP
-#define ACPI_TABLE_TYPE_HPP
+#ifndef ACPI_COMMON_TABLE_TYPE_HPP
+#define ACPI_COMMON_TABLE_TYPE_HPP
// IWYU pragma: private, include <acpi/acpi.hpp>
diff --git a/libs/acpi/acpi/common/vla_table.hpp b/libs/acpi/acpi/common/vla_table.hpp
new file mode 100644
index 0000000..a65a28e
--- /dev/null
+++ b/libs/acpi/acpi/common/vla_table.hpp
@@ -0,0 +1,115 @@
+#ifndef ACPI_COMMON_VLA_TABLE_HPP
+#define ACPI_COMMON_VLA_TABLE_HPP
+
+#include <acpi/common/basic_table.hpp>
+
+#include <cstddef>
+#include <iterator>
+
+namespace acpi
+{
+
+ template<typename EntryType>
+ struct vla_table_iterator
+ {
+ using iterator_category = std::forward_iterator_tag;
+ using value_type = EntryType;
+ using pointer = value_type *;
+ using reference = value_type &;
+ using difference_type = std::ptrdiff_t;
+
+ constexpr vla_table_iterator() noexcept = default;
+
+ constexpr vla_table_iterator(pointer entry, pointer limit) noexcept
+ : m_entry{entry}
+ , m_limit{limit}
+ {}
+
+ constexpr auto operator++() noexcept -> vla_table_iterator &
+ {
+ auto decayed = std::bit_cast<std::byte *>(m_entry);
+ decayed += m_entry->length();
+ m_entry = std::bit_cast<pointer>(decayed);
+ return *this;
+ }
+
+ constexpr auto operator++(int) noexcept -> vla_table_iterator
+ {
+ auto copy = *this;
+ ++*this;
+ return copy;
+ }
+
+ constexpr auto operator*() const noexcept -> reference
+ {
+ return *m_entry;
+ }
+
+ constexpr auto operator->() const noexcept -> pointer
+ {
+ return m_entry;
+ }
+
+ constexpr auto operator==(vla_table_iterator const & other) const noexcept -> bool
+ {
+ return m_entry == other.m_entry || (is_end() && other.is_end());
+ }
+
+ private:
+ [[nodiscard]] constexpr auto is_end() const noexcept -> bool
+ {
+ return m_entry == m_limit;
+ }
+
+ pointer m_entry{};
+ pointer m_limit{};
+ };
+
+ template<typename EntryType, typename TableType>
+ struct vla_table : basic_table<TableType>
+ {
+ using entry_type = EntryType;
+ using iterator = vla_table_iterator<EntryType>;
+ using const_iterator = vla_table_iterator<EntryType const>;
+
+ [[nodiscard]] auto begin() noexcept -> iterator
+ {
+ auto base = std::bit_cast<std::byte *>(this);
+ base += sizeof(TableType);
+ auto limit = std::bit_cast<std::byte *>(this);
+ limit += this->length().value;
+ return iterator{std::bit_cast<entry_type *>(base), std::bit_cast<entry_type *>(limit)};
+ }
+
+ [[nodiscard]] auto end() noexcept -> iterator
+ {
+ return iterator{};
+ }
+
+ [[nodiscard]] auto begin() const noexcept -> const_iterator
+ {
+ auto base = std::bit_cast<std::byte *>(this);
+ base += sizeof(TableType);
+ auto limit = std::bit_cast<std::byte *>(this);
+ limit += this->length().value;
+ return const_iterator{std::bit_cast<entry_type const *>(base), std::bit_cast<entry_type const *>(limit)};
+ }
+
+ [[nodiscard]] auto end() const noexcept -> const_iterator
+ {
+ return const_iterator{};
+ }
+
+ [[nodiscard]] auto cbegin() const noexcept -> const_iterator
+ {
+ return begin();
+ }
+
+ [[nodiscard]] auto cend() const noexcept -> const_iterator
+ {
+ return end();
+ }
+ };
+} // namespace acpi
+
+#endif \ No newline at end of file
diff --git a/libs/acpi/acpi/data/madt.cpp b/libs/acpi/acpi/data/madt.cpp
new file mode 100644
index 0000000..a8d4741
--- /dev/null
+++ b/libs/acpi/acpi/data/madt.cpp
@@ -0,0 +1,112 @@
+#include <kstd/cstring>
+
+#include <acpi/data/madt.hpp>
+
+#include <bit>
+#include <cstddef>
+#include <cstdint>
+#include <span>
+
+namespace acpi
+{
+
+ struct madt_data
+ {
+ std::uint32_t local_interrupt_controller_address;
+ std::uint32_t flags;
+ };
+
+ static_assert(sizeof(madt_data) == 8);
+
+ auto madt::local_interrupt_controller_address() const noexcept -> std::uintptr_t
+ {
+ using type = decltype(madt_data::local_interrupt_controller_address);
+
+ constexpr auto size = sizeof(type);
+ constexpr auto offset = offsetof(madt_data, local_interrupt_controller_address);
+
+ auto const data = std::span<std::byte const, size>{m_data.data() + offset, size};
+ auto value = type{};
+
+ kstd::libc::memcpy(&value, data.data(), size);
+
+ return value;
+ }
+
+ auto madt::flags() const noexcept -> std::uint32_t
+ {
+ using type = decltype(madt_data::flags);
+
+ constexpr auto size = sizeof(type);
+ constexpr auto offset = offsetof(madt_data, flags);
+
+ auto const data = std::span<std::byte const, size>{m_data.data() + offset, size};
+ auto value = type{};
+
+ kstd::libc::memcpy(&value, data.data(), size);
+
+ return value;
+ }
+
+ struct madt_entry_data
+ {
+ std::uint8_t type;
+ std::uint8_t length;
+ };
+
+ static_assert(sizeof(madt_entry_data) == 2);
+
+ auto madt_entry::type() const noexcept -> enum type
+ {
+ constexpr auto field_offset = offsetof(madt_entry_data, type);
+
+ return std::bit_cast<enum type>(*(m_data.data() + field_offset));
+ }
+
+ auto madt_entry::length() const noexcept -> std::size_t
+ {
+ constexpr auto field_offset = offsetof(madt_entry_data, length);
+
+ return static_cast<std::size_t>(*(m_data.data() + field_offset));
+ }
+
+ struct [[gnu::packed]] processor_local_apic_entry_data
+ {
+ std::uint8_t apic_id;
+ std::uint8_t processor_id;
+ std::uint32_t flags;
+ };
+
+ static_assert(sizeof(processor_local_apic_entry_data) == 6);
+
+ auto processor_local_apic_entry::id() const noexcept -> std::uint8_t
+ {
+ constexpr auto field_offset = offsetof(processor_local_apic_entry_data, apic_id);
+
+ return static_cast<std::uint8_t>(*(m_data.data() + field_offset));
+ }
+
+ auto processor_local_apic_entry::flags() const noexcept -> enum flags
+ {
+ using type = decltype(processor_local_apic_entry_data::flags);
+ static_assert(sizeof(type) == sizeof(enum flags));
+
+ constexpr auto size = sizeof(type);
+ constexpr auto offset = offsetof(processor_local_apic_entry_data, flags);
+
+ auto const data = std::span<std::byte const, size>{m_data.data() + offset, size};
+ auto value = type{};
+
+ kstd::libc::memcpy(&value, data.data(), size);
+
+ return static_cast<enum flags>(value);
+ }
+
+ auto processor_local_apic_entry::processor_id() const noexcept -> std::uint32_t
+ {
+ constexpr auto field_offset = offsetof(processor_local_apic_entry_data, processor_id);
+
+ return static_cast<std::uint32_t>(*(m_data.data() + field_offset));
+ }
+
+} // namespace acpi
diff --git a/libs/acpi/acpi/data/madt.hpp b/libs/acpi/acpi/data/madt.hpp
new file mode 100644
index 0000000..8307826
--- /dev/null
+++ b/libs/acpi/acpi/data/madt.hpp
@@ -0,0 +1,117 @@
+#ifndef ACPI_ACPI_MADT_HPP
+#define ACPI_ACPI_MADT_HPP
+
+// IWYU pragma: private, include <acpi/acpi.hpp>
+
+#include <kstd/ext/bitfield_enum>
+#include <kstd/os/error.hpp>
+#include <kstd/units>
+
+#include <acpi/common/table_signature.hpp>
+#include <acpi/common/table_type.hpp>
+#include <acpi/common/vla_table.hpp>
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <ranges>
+#include <type_traits>
+
+namespace acpi
+{
+
+ template<>
+ struct table_signature<struct madt>
+ {
+ constexpr char static const value[] = "APIC"; // NOLINT
+ };
+
+ template<>
+ struct table_type<table_signature_v<struct madt>>
+ {
+ using type = struct madt;
+ };
+
+ struct madt_entry
+ {
+ //! The type of an MADT entry.
+ enum struct type : std::uint8_t
+ {
+ processor_local_apic,
+ io_apic,
+ io_apic_interrupt_source_override,
+ io_apic_nmi_source,
+ local_apic_nmi_interrupts,
+ local_apic_address_override,
+ processor_local_x2_apic,
+ };
+
+ //! Get the type of this entry.
+ [[nodiscard]] auto type() const noexcept -> enum type;
+
+ //! Get the length of this entry.
+ [[nodiscard]] auto length() const noexcept -> std::size_t;
+
+ //! Cast this entry to the given concrete entry type.
+ //!
+ //! @warning This function will terminate execution if the desired target type does not match up with this entry's
+ //! actual type.
+ template<typename EntryType>
+ [[nodiscard]] auto as() const -> EntryType const &
+ {
+ if (type() != EntryType::this_type)
+ {
+ kstd::os::panic("Invalid cast");
+ }
+ return reinterpret_cast<EntryType const &>(*this);
+ }
+
+ private:
+ std::array<std::byte, 2> m_data{};
+ };
+
+ //! The Multiple APIC Description Table (MADT)
+ struct madt : vla_table<madt_entry, madt>
+ {
+ //! Get the physical address of the local interrupt controllers described by this entry.
+ [[nodiscard]] auto local_interrupt_controller_address() const noexcept -> std::uintptr_t;
+
+ [[nodiscard]] auto flags() const noexcept -> std::uint32_t;
+
+ template<typename EntryType>
+ [[nodiscard]] auto only() const noexcept
+ {
+ return *this | std::views::filter([](auto const & e) { return e.type() == EntryType::this_type; }) |
+ std::views::transform([](auto const & e) { return e.template as<EntryType>(); });
+ }
+
+ private:
+ std::array<std::byte, 8> m_data{};
+ };
+
+ struct processor_local_apic_entry : madt_entry
+ {
+ constexpr auto static this_type = type::processor_local_apic;
+
+ enum struct flags : std::uint32_t
+ {
+ processor_enabled = 1,
+ online_capable = 2,
+ };
+
+ [[nodiscard]] auto id() const noexcept -> std::uint8_t;
+ [[nodiscard]] auto flags() const noexcept -> flags;
+ [[nodiscard]] auto processor_id() const noexcept -> std::uint32_t;
+
+ private:
+ std::array<std::byte, 6> m_data{};
+ };
+
+} // namespace acpi
+
+template<>
+struct kstd::ext::is_bitfield_enum<enum acpi::processor_local_apic_entry::flags> : std::true_type
+{
+};
+
+#endif
diff --git a/libs/acpi/acpi/data/madt.test.cpp b/libs/acpi/acpi/data/madt.test.cpp
new file mode 100644
index 0000000..6795499
--- /dev/null
+++ b/libs/acpi/acpi/data/madt.test.cpp
@@ -0,0 +1,55 @@
+#include <kstd/units>
+
+#include <acpi/data/madt.hpp>
+#include <catch2/catch_test_macros.hpp>
+#include <test_data/tables.hpp>
+
+#include <iterator>
+
+SCENARIO("MADT parsing", "[madt]")
+{
+ GIVEN("The basic compiled MADT containing a single LAPIC entry and the default x86 LAPIC address")
+ {
+ auto data = acpi::test_data::tables::basic_madt();
+
+ WHEN("parsing the table")
+ {
+ auto madt = reinterpret_cast<acpi::madt const *>(data.data());
+
+ THEN("the signature is correct")
+ {
+ REQUIRE(madt->signature() == "APIC");
+ }
+
+ THEN("validate returns true")
+ {
+ REQUIRE(madt->validate());
+ }
+
+ THEN("there is a single entry in the table")
+ {
+ REQUIRE(std::distance(madt->begin(), madt->end()) == 1);
+ }
+
+ THEN("the LAPIC address is 0xfee00000")
+ {
+ REQUIRE(madt->local_interrupt_controller_address() == 0xfee0'0000);
+ }
+
+ THEN("the length is sizeof(madt) + sizeof(processor_local_apic)")
+ {
+ REQUIRE(madt->length().value == sizeof(acpi::madt) + sizeof(acpi::processor_local_apic_entry));
+ }
+
+ THEN("the first entry has type processor_local_apic")
+ {
+ REQUIRE(madt->cbegin()->type() == acpi::madt_entry::type::processor_local_apic);
+ }
+
+ THEN("`only` can be used to get a view of all processor_local_apic entries")
+ {
+ REQUIRE(std::ranges::distance(madt->only<acpi::processor_local_apic_entry>()) == 1);
+ }
+ }
+ }
+}
diff --git a/libs/acpi/acpi/data/rsdt.cpp b/libs/acpi/acpi/data/rsdt.cpp
new file mode 100644
index 0000000..fe108c7
--- /dev/null
+++ b/libs/acpi/acpi/data/rsdt.cpp
@@ -0,0 +1,38 @@
+#include <kstd/cstring>
+
+#include <acpi/common/table_header.hpp>
+#include <acpi/data/rsdt.hpp>
+
+#include <cstddef>
+#include <cstdint>
+#include <span>
+
+namespace acpi
+{
+
+ struct rsdt_entry_data
+ {
+ std::uint32_t address;
+ };
+
+ [[nodiscard]] auto rsdt_entry::address() const noexcept -> table_header *
+ {
+ using type = decltype(rsdt_entry_data::address);
+
+ constexpr auto size = sizeof(type);
+ constexpr auto offset = offsetof(rsdt_entry_data, address);
+
+ auto const data = std::span<std::byte const, size>{m_data.data() + offset, size};
+ auto value = type{};
+
+ kstd::libc::memcpy(&value, data.data(), size);
+
+ return reinterpret_cast<table_header *>(static_cast<uintptr_t>(value));
+ }
+
+ [[nodiscard]] auto rsdt_entry::length() const noexcept -> std::size_t
+ {
+ return sizeof(m_data);
+ }
+
+} // namespace acpi \ No newline at end of file
diff --git a/libs/acpi/acpi/data/rsdt.hpp b/libs/acpi/acpi/data/rsdt.hpp
new file mode 100644
index 0000000..a661187
--- /dev/null
+++ b/libs/acpi/acpi/data/rsdt.hpp
@@ -0,0 +1,42 @@
+#ifndef ACPI_DATA_RSDT_HPP
+#define ACPI_DATA_RSDT_HPP
+
+#include <acpi/common/table_header.hpp>
+#include <acpi/common/table_signature.hpp>
+#include <acpi/common/table_type.hpp>
+#include <acpi/common/vla_table.hpp>
+
+#include <array>
+#include <cstddef>
+
+namespace acpi
+{
+
+ template<>
+ struct table_signature<struct rsdt>
+ {
+ constexpr char static const value[] = "RSDT"; // NOLINT
+ };
+
+ template<>
+ struct table_type<table_signature_v<struct rsdt>>
+ {
+ using type = struct rsdt;
+ };
+
+ struct rsdt_entry
+ {
+ [[nodiscard]] auto address() const noexcept -> table_header *;
+ [[nodiscard]] auto length() const noexcept -> std::size_t;
+
+ private:
+ std::array<std::byte, 4> m_data{};
+ };
+
+ struct rsdt : vla_table<rsdt_entry, rsdt>
+ {
+ };
+
+} // namespace acpi
+
+#endif \ No newline at end of file
diff --git a/libs/acpi/acpi/data/rsdt.test.cpp b/libs/acpi/acpi/data/rsdt.test.cpp
new file mode 100644
index 0000000..937dce0
--- /dev/null
+++ b/libs/acpi/acpi/data/rsdt.test.cpp
@@ -0,0 +1,46 @@
+#include <kstd/units>
+
+#include <acpi/acpi.hpp>
+#include <acpi/data/rsdt.hpp>
+#include <catch2/catch_test_macros.hpp>
+#include <test_data/tables.hpp>
+
+#include <iterator>
+
+SCENARIO("RSDT parsing", "[rsdt]")
+{
+ GIVEN("The basic compiled RSDT containing 8 table pointers")
+ {
+ auto data = acpi::test_data::tables::basic_rsdt();
+
+ WHEN("parsing the table")
+ {
+ auto rsdt = reinterpret_cast<acpi::rsdt const *>(data.data());
+
+ THEN("the signature is correct")
+ {
+ REQUIRE(rsdt->signature() == "RSDT");
+ }
+
+ THEN("validate returns true")
+ {
+ REQUIRE(rsdt->validate());
+ }
+
+ THEN("there are 8 entries in the table")
+ {
+ REQUIRE(std::distance(rsdt->begin(), rsdt->end()) == 8);
+ }
+
+ THEN("the first entry has address 0x10")
+ {
+ REQUIRE(rsdt->cbegin()->address() == reinterpret_cast<acpi::table_header *>(0x10));
+ }
+
+ THEN("the length is sizeof(rsdt) + 8 * sizeof(rsdt_entry)")
+ {
+ REQUIRE(rsdt->length().value == sizeof(acpi::rsdt) + 8 * sizeof(acpi::rsdt_entry));
+ }
+ }
+ }
+}
diff --git a/libs/acpi/acpi/data/xsdt.cpp b/libs/acpi/acpi/data/xsdt.cpp
new file mode 100644
index 0000000..b4202b8
--- /dev/null
+++ b/libs/acpi/acpi/data/xsdt.cpp
@@ -0,0 +1,38 @@
+#include <kstd/cstring>
+
+#include <acpi/common/table_header.hpp>
+#include <acpi/data/xsdt.hpp>
+
+#include <cstddef>
+#include <cstdint>
+#include <span>
+
+namespace acpi
+{
+
+ struct xsdt_entry_data
+ {
+ std::uint64_t address;
+ };
+
+ [[nodiscard]] auto xsdt_entry::address() const noexcept -> table_header *
+ {
+ using type = decltype(xsdt_entry_data::address);
+
+ constexpr auto size = sizeof(type);
+ constexpr auto offset = offsetof(xsdt_entry_data, address);
+
+ auto const data = std::span<std::byte const, size>{m_data.data() + offset, size};
+ auto value = type{};
+
+ kstd::libc::memcpy(&value, data.data(), size);
+
+ return reinterpret_cast<table_header *>(static_cast<uintptr_t>(value));
+ }
+
+ [[nodiscard]] auto xsdt_entry::length() const noexcept -> std::size_t
+ {
+ return sizeof(m_data);
+ }
+
+} // namespace acpi \ No newline at end of file
diff --git a/libs/acpi/acpi/data/xsdt.hpp b/libs/acpi/acpi/data/xsdt.hpp
new file mode 100644
index 0000000..965f23c
--- /dev/null
+++ b/libs/acpi/acpi/data/xsdt.hpp
@@ -0,0 +1,42 @@
+#ifndef ACPI_DATA_XSDT_HPP
+#define ACPI_DATA_XSDT_HPP
+
+#include <acpi/common/table_header.hpp>
+#include <acpi/common/table_signature.hpp>
+#include <acpi/common/table_type.hpp>
+#include <acpi/common/vla_table.hpp>
+
+#include <array>
+#include <cstddef>
+
+namespace acpi
+{
+
+ template<>
+ struct table_signature<struct xsdt>
+ {
+ constexpr char static const value[] = "XSDT"; // NOLINT
+ };
+
+ template<>
+ struct table_type<table_signature_v<struct xsdt>>
+ {
+ using type = struct xsdt;
+ };
+
+ struct xsdt_entry
+ {
+ [[nodiscard]] auto address() const noexcept -> table_header *;
+ [[nodiscard]] auto length() const noexcept -> std::size_t;
+
+ private:
+ std::array<std::byte, 8> m_data{};
+ };
+
+ struct xsdt : vla_table<xsdt_entry, xsdt>
+ {
+ };
+
+} // namespace acpi
+
+#endif \ No newline at end of file
diff --git a/libs/acpi/acpi/data/xsdt.test.cpp b/libs/acpi/acpi/data/xsdt.test.cpp
new file mode 100644
index 0000000..7fb564c
--- /dev/null
+++ b/libs/acpi/acpi/data/xsdt.test.cpp
@@ -0,0 +1,46 @@
+#include <kstd/units>
+
+#include <acpi/acpi.hpp>
+#include <acpi/data/xsdt.hpp>
+#include <catch2/catch_test_macros.hpp>
+#include <test_data/tables.hpp>
+
+#include <iterator>
+
+SCENARIO("XSDT parsing", "[xsdt]")
+{
+ GIVEN("The basic compiled XSDT containing 8 table pointers")
+ {
+ auto data = acpi::test_data::tables::basic_xsdt();
+
+ WHEN("parsing the table")
+ {
+ auto xsdt = reinterpret_cast<acpi::xsdt const *>(data.data());
+
+ THEN("the signature is correct")
+ {
+ REQUIRE(xsdt->signature() == "XSDT");
+ }
+
+ THEN("validate returns true")
+ {
+ REQUIRE(xsdt->validate());
+ }
+
+ THEN("there are 8 entries in the table")
+ {
+ REQUIRE(std::distance(xsdt->begin(), xsdt->end()) == 8);
+ }
+
+ THEN("the first entry has address 0x10")
+ {
+ REQUIRE(xsdt->cbegin()->address() == reinterpret_cast<acpi::table_header *>(0x10));
+ }
+
+ THEN("the length is sizeof(xsdt) + 8 * sizeof(xsdt_entry)")
+ {
+ REQUIRE(xsdt->length().value == sizeof(acpi::xsdt) + 8 * sizeof(acpi::xsdt_entry));
+ }
+ }
+ }
+}
diff --git a/libs/acpi/acpi/madt.cpp b/libs/acpi/acpi/madt.cpp
deleted file mode 100644
index 40289cf..0000000
--- a/libs/acpi/acpi/madt.cpp
+++ /dev/null
@@ -1,68 +0,0 @@
-#include <acpi/madt.hpp>
-
-#include <cstddef>
-#include <cstdint>
-
-namespace acpi
-{
-
- auto madt::local_interrupt_controller_address() const noexcept -> std::uintptr_t
- {
- return static_cast<std::uintptr_t>(m_local_interrupt_controller_address);
- }
-
- auto madt::flags() const noexcept -> std::uint32_t
- {
- return m_flags;
- }
-
- auto madt_entry::type() const noexcept -> types
- {
- return static_cast<types>(m_type);
- }
-
- auto madt_entry::length() const noexcept -> std::size_t
- {
- return m_length;
- }
-
- auto processor_local_apic::apic_id() const noexcept -> std::uint8_t
- {
- return m_apic_id;
- }
-
- auto processor_local_apic::active_flags() const noexcept -> flags
- {
- return static_cast<flags>(m_flags);
- }
-
- auto processor_local_apic::processor_id() const noexcept -> std::uint32_t
- {
- return m_processor_id;
- }
-
- auto madt::begin() const noexcept -> iterator
- {
- auto base = reinterpret_cast<std::byte const *>(this);
- base += sizeof(madt);
- auto limit = reinterpret_cast<std::byte const *>(this);
- limit += length().value;
- return iterator{reinterpret_cast<madt_entry const *>(base), reinterpret_cast<madt_entry const *>(limit)};
- }
-
- auto madt::cbegin() const noexcept -> iterator
- {
- return begin();
- }
-
- auto madt::end() const noexcept -> iterator
- {
- return {};
- }
-
- auto madt::cend() const noexcept -> iterator
- {
- return end();
- }
-
-} // namespace acpi
diff --git a/libs/acpi/acpi/madt.hpp b/libs/acpi/acpi/madt.hpp
deleted file mode 100644
index f680430..0000000
--- a/libs/acpi/acpi/madt.hpp
+++ /dev/null
@@ -1,92 +0,0 @@
-#ifndef ACPI_ACPI_MADT_HPP
-#define ACPI_ACPI_MADT_HPP
-
-// IWYU pragma: private, include <acpi/acpi.hpp>
-
-#include <kstd/ext/bitfield_enum>
-#include <kstd/units>
-
-#include <acpi/sdt.hpp>
-#include <acpi/table_type.hpp>
-
-#include <cstddef>
-#include <cstdint>
-#include <type_traits>
-
-namespace acpi
-{
-
- struct [[gnu::packed]] madt_entry
- {
- enum struct types : std::uint8_t
- {
- processor_local_apic,
- io_apic,
- io_apic_interrupt_source_override,
- io_apic_nmi_source,
- local_apic_nmi_interrupts,
- local_apic_address_override,
- processor_local_x2_apic,
- };
-
- [[nodiscard]] auto type() const noexcept -> types;
- [[nodiscard]] auto length() const noexcept -> std::size_t;
-
- private:
- std::uint8_t m_type;
- std::uint8_t m_length;
- };
-
- //! The common header for all
- struct [[gnu::packed]] madt : sdt
- {
- using iterator = sdt_iterator<madt_entry const>;
- using const_iterator = iterator;
-
- [[nodiscard]] auto local_interrupt_controller_address() const noexcept -> std::uintptr_t;
- [[nodiscard]] auto flags() const noexcept -> std::uint32_t;
-
- [[nodiscard]] auto begin() const noexcept -> iterator;
- [[nodiscard]] auto cbegin() const noexcept -> iterator;
- [[nodiscard]] auto end() const noexcept -> iterator;
- [[nodiscard]] auto cend() const noexcept -> iterator;
-
- private:
- std::uint32_t m_local_interrupt_controller_address;
- std::uint32_t m_flags;
- };
-
- struct [[gnu::packed]] processor_local_apic : madt_entry
- {
- enum struct flags : std::uint32_t
- {
- processor_enabled = 1,
- online_capable = 2,
- };
-
- [[nodiscard]] auto apic_id() const noexcept -> std::uint8_t;
- [[nodiscard]] auto active_flags() const noexcept -> flags;
- [[nodiscard]] auto processor_id() const noexcept -> std::uint32_t;
-
- private:
- std::uint8_t m_processor_id;
- std::uint8_t m_apic_id;
- std::uint32_t m_flags;
- };
-
- constexpr char const madt_table_signature[] = "APIC"; // NOLINT
-
- template<>
- struct table_type<madt_table_signature>
- {
- using type = madt;
- };
-
-} // namespace acpi
-
-template<>
-struct kstd::ext::is_bitfield_enum<acpi::processor_local_apic::flags> : std::true_type
-{
-};
-
-#endif
diff --git a/libs/acpi/acpi/pointers.cpp b/libs/acpi/acpi/pointers.cpp
index b206cc6..45a42ce 100644
--- a/libs/acpi/acpi/pointers.cpp
+++ b/libs/acpi/acpi/pointers.cpp
@@ -1,6 +1,6 @@
#include <kstd/units>
-#include <acpi/checksum.hpp>
+#include <acpi/common/checksum.hpp>
#include <acpi/pointers.hpp>
#include <bit>
@@ -34,7 +34,7 @@ namespace acpi
auto rsdp::validate() const noexcept -> bool
{
- return validate_checksum({reinterpret_cast<std::byte const *>(this), sizeof(rsdp)});
+ return signature() == "RSD PTR " && validate_checksum({reinterpret_cast<std::byte const *>(this), sizeof(rsdp)});
}
auto xsdp::length() const noexcept -> kstd::units::bytes
@@ -49,7 +49,7 @@ namespace acpi
auto xsdp::validate() const noexcept -> bool
{
- return validate_checksum({reinterpret_cast<std::byte const *>(this), m_length});
+ return signature() == "RSD PTR " && validate_checksum({reinterpret_cast<std::byte const *>(this), m_length});
}
} // namespace acpi
diff --git a/libs/acpi/acpi/pointers.test.cpp b/libs/acpi/acpi/pointers.test.cpp
new file mode 100644
index 0000000..06ce1a4
--- /dev/null
+++ b/libs/acpi/acpi/pointers.test.cpp
@@ -0,0 +1,29 @@
+#include <acpi/pointers.hpp>
+#include <catch2/catch_test_macros.hpp>
+
+#include <array>
+#include <bit>
+#include <cstddef>
+
+SCENARIO("ACPI root pointer parsing", "[acpi]")
+{
+ GIVEN("A null-filled pointer")
+ {
+ auto data = std::array<std::byte, sizeof(acpi::rsdp)>{};
+
+ WHEN("parsing the data")
+ {
+ auto rsdp = std::bit_cast<acpi::rsdp>(data);
+
+ THEN("the signature is invalid")
+ {
+ REQUIRE(rsdp.signature() != "RSD PTR ");
+ }
+
+ THEN("validate returns false")
+ {
+ REQUIRE_FALSE(rsdp.validate());
+ }
+ }
+ }
+}
diff --git a/libs/acpi/acpi/sdt.cpp b/libs/acpi/acpi/sdt.cpp
deleted file mode 100644
index c2b9d68..0000000
--- a/libs/acpi/acpi/sdt.cpp
+++ /dev/null
@@ -1,51 +0,0 @@
-#include <kstd/units>
-
-#include <acpi/sdt.hpp>
-
-#include <cstdint>
-#include <string_view>
-
-namespace acpi
-{
-
- auto sdt::creator_revision() const noexcept -> std::uint32_t
- {
- return m_creator_revision;
- }
-
- auto sdt::creator_id() const noexcept -> std::uint32_t
- {
- return m_creator_id;
- }
-
- auto sdt::length() const noexcept -> kstd::units::bytes
- {
- return kstd::units::bytes{m_length};
- }
-
- auto sdt::oem_id() const noexcept -> std::string_view
- {
- return {m_oem_id.data(), m_oem_id.size()};
- }
-
- auto sdt::oem_revision() const noexcept -> std::uint32_t
- {
- return m_oem_revision;
- }
-
- auto sdt::oem_table_id() const noexcept -> std::string_view
- {
- return {m_oem_table_id.data(), m_oem_table_id.size()};
- }
-
- auto sdt::revision() const noexcept -> std::uint8_t
- {
- return m_revision;
- }
-
- auto sdt::signature() const noexcept -> std::string_view
- {
- return {m_signature.data(), m_signature.size()};
- }
-
-} // namespace acpi
diff --git a/libs/acpi/acpi/sdt.hpp b/libs/acpi/acpi/sdt.hpp
deleted file mode 100644
index 9e5ec89..0000000
--- a/libs/acpi/acpi/sdt.hpp
+++ /dev/null
@@ -1,119 +0,0 @@
-#ifndef ACPI_SDT_HPP
-#define ACPI_SDT_HPP
-
-// IWYU pragma: private, include <acpi/acpi.hpp>
-
-#include <kstd/units>
-
-#include <acpi/table_type.hpp>
-
-#include <array>
-#include <cstddef>
-#include <cstdint>
-#include <iterator>
-#include <string_view>
-
-namespace acpi
-{
-
- template<typename EntryType>
- struct sdt_iterator
- {
- using iterator_category = std::forward_iterator_tag;
- using value_type = EntryType;
- using pointer = value_type *;
- using reference = value_type &;
- using difference_type = std::ptrdiff_t;
-
- constexpr sdt_iterator() noexcept = default;
-
- constexpr sdt_iterator(pointer entry, pointer limit) noexcept
- : m_entry{entry}
- , m_limit{limit}
- {}
-
- constexpr auto operator++() noexcept -> sdt_iterator &
- {
- auto decayed = std::bit_cast<std::byte *>(m_entry);
- decayed += m_entry->length();
- m_entry = std::bit_cast<pointer>(decayed);
- return *this;
- }
-
- constexpr auto operator++(int) noexcept -> sdt_iterator
- {
- auto copy = *this;
- ++*this;
- return copy;
- }
-
- constexpr auto operator*() const noexcept -> reference
- {
- return *m_entry;
- }
-
- constexpr auto operator==(sdt_iterator const & other) const noexcept -> bool
- {
- return m_entry == other.m_entry || (is_end() && other.is_end());
- }
-
- private:
- [[nodiscard]] constexpr auto is_end() const noexcept -> bool
- {
- return m_entry == m_limit;
- }
-
- pointer m_entry{};
- pointer m_limit{};
- };
-
- //! The common base of all System Description Tables.
- struct [[gnu::packed]] sdt
- {
- //! Get the revision of the utility used to create this table.
- [[nodiscard]] auto creator_revision() const noexcept -> std::uint32_t;
-
- //! Get the vendor ID of the utility used to create this table.
- [[nodiscard]] auto creator_id() const noexcept -> std::uint32_t;
-
- //! Get the length of the entire table, including this header.
- [[nodiscard]] auto length() const noexcept -> kstd::units::bytes;
-
- //! Get the ID of the OEM.
- [[nodiscard]] auto oem_id() const noexcept -> std::string_view;
-
- //! Get the OEMs revision number of this table.
- [[nodiscard]] auto oem_revision() const noexcept -> std::uint32_t;
-
- //! Get the OEMs ID of this table.
- [[nodiscard]] auto oem_table_id() const noexcept -> std::string_view;
-
- //! Get the revision number of the structure of this table.
- [[nodiscard]] auto revision() const noexcept -> std::uint8_t;
-
- //! Get the signature of this table.
- [[nodiscard]] auto signature() const noexcept -> std::string_view;
-
- private:
- std::array<char, 4> m_signature;
- std::uint32_t m_length;
- std::uint8_t m_revision;
- [[maybe_unused]] std::uint8_t m_checksum;
- std::array<char, 6> m_oem_id;
- std::array<char, 8> m_oem_table_id;
- std::uint32_t m_oem_revision;
- std::uint32_t m_creator_id;
- std::uint32_t m_creator_revision;
- };
-
- constexpr char const rsdt_table_signature[] = "RSDT"; // NOLINT
-
- template<>
- struct table_type<rsdt_table_signature>
- {
- using type = sdt;
- };
-
-} // namespace acpi
-
-#endif
diff --git a/libs/acpi/test_data/basic_madt.asl b/libs/acpi/test_data/basic_madt.asl
new file mode 100644
index 0000000..cd6958a
--- /dev/null
+++ b/libs/acpi/test_data/basic_madt.asl
@@ -0,0 +1,29 @@
+/*
+ * Intel ACPI Component Architecture
+ * iASL Compiler/Disassembler version 20251212 (64-bit version)
+ * Copyright (c) 2000 - 2025 Intel Corporation
+ *
+ * Template for [APIC] ACPI Table (static data table)
+ * Format: [ByteLength] FieldName : HexFieldValue
+ */
+[0004] Signature : "APIC" [Multiple APIC Description Table (MADT)]
+[0004] Table Length : 00000000
+[0001] Revision : 07
+[0001] Checksum : 00
+[0006] Oem ID : "INTEL "
+[0008] Oem Table ID : "Template"
+[0004] Oem Revision : 00000001
+[0004] Asl Compiler ID : "INTL"
+[0004] Asl Compiler Revision : 20230628
+
+[0004] Local Apic Address : FEE00000
+[0004] Flags (decoded below) : 00000001
+ PC-AT Compatibility : 1
+
+[0001] Subtable Type : 00 [Processor Local APIC]
+[0001] Length : 08
+[0001] Processor ID : 00
+[0001] Local Apic ID : 00
+[0004] Flags (decoded below) : 00000001
+ Processor Enabled : 1
+ Runtime Online Capable : 0
diff --git a/libs/acpi/test_data/basic_rsdp.asl b/libs/acpi/test_data/basic_rsdp.asl
new file mode 100644
index 0000000..8274c0f
--- /dev/null
+++ b/libs/acpi/test_data/basic_rsdp.asl
@@ -0,0 +1,16 @@
+/*
+ * Intel ACPI Component Architecture
+ * iASL Compiler/Disassembler version 20251212 (64-bit version)
+ * Copyright (c) 2000 - 2025 Intel Corporation
+ *
+ * Template for [RSDP] ACPI Table (AML byte code table)
+ */
+[0008] Signature : "RSD PTR "
+[0001] Checksum : 43
+[0006] Oem ID : "INTEL "
+[0001] Revision : 02
+[0004] RSDT Address : 00000000
+[0004] Length : 00000024
+[0008] XSDT Address : 0000000000000000
+[0001] Extended Checksum : DC
+[0003] Reserved : 000000
diff --git a/libs/acpi/test_data/basic_rsdt.asl b/libs/acpi/test_data/basic_rsdt.asl
new file mode 100644
index 0000000..6cf4c7a
--- /dev/null
+++ b/libs/acpi/test_data/basic_rsdt.asl
@@ -0,0 +1,26 @@
+/*
+ * Intel ACPI Component Architecture
+ * iASL Compiler/Disassembler version 20251212 (64-bit version)
+ * Copyright (c) 2000 - 2025 Intel Corporation
+ *
+ * Template for [RSDT] ACPI Table (static data table)
+ * Format: [ByteLength] FieldName : HexFieldValue
+ */
+[0004] Signature : "RSDT" [Root System Description Table]
+[0004] Table Length : 00000000
+[0001] Revision : 01
+[0001] Checksum : 00
+[0006] Oem ID : "INTEL "
+[0008] Oem Table ID : "TEMPLATE"
+[0004] Oem Revision : 00000001
+[0004] Asl Compiler ID : "INTL"
+[0004] Asl Compiler Revision : 20100528
+
+[0004] ACPI Table Address 0 : 00000010
+[0004] ACPI Table Address 1 : 00000020
+[0004] ACPI Table Address 2 : 00000030
+[0004] ACPI Table Address 3 : 00000040
+[0004] ACPI Table Address 4 : 00000050
+[0004] ACPI Table Address 5 : 00000060
+[0004] ACPI Table Address 6 : 00000070
+[0004] ACPI Table Address 7 : 00000080
diff --git a/libs/acpi/test_data/basic_xsdt.asl b/libs/acpi/test_data/basic_xsdt.asl
new file mode 100644
index 0000000..d8589f9
--- /dev/null
+++ b/libs/acpi/test_data/basic_xsdt.asl
@@ -0,0 +1,26 @@
+/*
+ * Intel ACPI Component Architecture
+ * iASL Compiler/Disassembler version 20251212 (64-bit version)
+ * Copyright (c) 2000 - 2025 Intel Corporation
+ *
+ * Template for [XSDT] ACPI Table (static data table)
+ * Format: [ByteLength] FieldName : HexFieldValue
+ */
+[0004] Signature : "XSDT" [Extended System Description Table]
+[0004] Table Length : 00000064
+[0001] Revision : 01
+[0001] Checksum : 8B
+[0006] Oem ID : "INTEL "
+[0008] Oem Table ID : "TEMPLATE"
+[0004] Oem Revision : 00000001
+[0004] Asl Compiler ID : "INTL"
+[0004] Asl Compiler Revision : 20100528
+
+[0008] ACPI Table Address 0 : 0000000000000010
+[0008] ACPI Table Address 1 : 0000000000000020
+[0008] ACPI Table Address 2 : 0000000000000030
+[0008] ACPI Table Address 3 : 0000000000000040
+[0008] ACPI Table Address 4 : 0000000000000050
+[0008] ACPI Table Address 5 : 0000000000000060
+[0008] ACPI Table Address 6 : 0000000000000070
+[0008] ACPI Table Address 7 : 0000000000000080
diff --git a/libs/acpi/test_data/table_header.asl b/libs/acpi/test_data/table_header.asl
new file mode 100644
index 0000000..8cddc03
--- /dev/null
+++ b/libs/acpi/test_data/table_header.asl
@@ -0,0 +1,9 @@
+[0004] Signature : "TEST"
+[0004] Table Length : 00000000
+[0001] Revision : 01
+[0001] Checksum : 00
+[0006] Oem ID : "FEMO "
+[0008] Oem Table ID : "HDRTEST "
+[0004] Oem Revision : 00000001
+[0004] Asl Compiler ID : "INTL"
+[0004] Asl Compiler Revision : 00000000
diff --git a/libs/acpi/test_data/tables.S b/libs/acpi/test_data/tables.S
new file mode 100644
index 0000000..641db6a
--- /dev/null
+++ b/libs/acpi/test_data/tables.S
@@ -0,0 +1,17 @@
+.section .rodata
+
+.balign 16
+
+#define TABLE(name, file) \
+ .global name##_start; \
+ .global name##_end; \
+ name##_start: .incbin file; \
+ name##_end:
+
+TABLE(basic_madt, "basic_madt.aml")
+TABLE(basic_rsdt, "basic_rsdt.aml")
+TABLE(basic_rsdp, "basic_rsdp.aml")
+TABLE(basic_xsdt, "basic_xsdt.aml")
+TABLE(table_header, "table_header.aml")
+
+#undef TABLE
diff --git a/libs/acpi/test_data/tables.hpp b/libs/acpi/test_data/tables.hpp
new file mode 100644
index 0000000..e91f1a5
--- /dev/null
+++ b/libs/acpi/test_data/tables.hpp
@@ -0,0 +1,28 @@
+#ifndef ACPI_TEST_DATA_TABLES_HPP
+#define ACPI_TEST_DATA_TABLES_HPP
+
+#include <cstddef>
+#include <span>
+
+#define TABLE(name) \
+ extern "C" std::byte const name##_start; \
+ extern "C" std::byte const name##_end; \
+ auto inline name()->std::span<std::byte const> \
+ { \
+ return {&name##_start, &name##_end}; \
+ }
+
+namespace acpi::test_data::tables
+{
+
+ TABLE(basic_madt);
+ TABLE(basic_rsdt);
+ TABLE(basic_rsdp);
+ TABLE(basic_xsdt);
+ TABLE(table_header);
+
+} // namespace acpi::test_data::tables
+
+#undef TABLE
+
+#endif
diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt
index 240118e..ced3138 100644
--- a/libs/kstd/CMakeLists.txt
+++ b/libs/kstd/CMakeLists.txt
@@ -21,6 +21,7 @@ target_sources("kstd" PRIVATE
"src/os/error.cpp"
"src/mutex.cpp"
+ "src/vformat.cpp"
)
file(GLOB_RECURSE KSTD_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "include/kstd/*")
@@ -43,6 +44,7 @@ if(CMAKE_CROSSCOMPILING)
else()
add_executable("kstd_tests"
"tests/src/flat_map.cpp"
+ "tests/src/format.cpp"
"tests/src/vector.cpp"
"tests/src/observer_ptr.cpp"
"tests/src/os_panic.cpp"
diff --git a/libs/kstd/include/kstd/bits/format/context.hpp b/libs/kstd/include/kstd/bits/format/context.hpp
index 478a48f..7f392a0 100644
--- a/libs/kstd/include/kstd/bits/format/context.hpp
+++ b/libs/kstd/include/kstd/bits/format/context.hpp
@@ -4,6 +4,7 @@
// IWYU pragma: private, include <kstd/format>
#include "arg.hpp"
+#include "kstd/bits/format/output_buffer.hpp"
#include <kstd/os/error.hpp>
@@ -29,13 +30,14 @@ namespace kstd
struct format_context
{
- using writer_function = void(void *, std::string_view);
using format_args = std::span<format_arg const>;
-
- writer_function * writer{};
- void * user_data{};
format_args args{};
+ format_context(bits::format::output_buffer & buffer, format_args args)
+ : args{args}
+ , m_buffer{&buffer}
+ {}
+
[[nodiscard]] auto arg(std::size_t const id) const -> format_arg const &
{
if (id >= args.size())
@@ -47,13 +49,16 @@ namespace kstd
constexpr auto push(std::string_view string) -> void
{
- writer(user_data, string);
+ m_buffer->push(string);
}
constexpr auto push(char character) -> void
{
- writer(user_data, std::string_view(&character, 1));
+ m_buffer->push(character);
}
+
+ private:
+ bits::format::output_buffer * m_buffer;
};
} // namespace kstd
diff --git a/libs/kstd/include/kstd/bits/format/formatter/bool.hpp b/libs/kstd/include/kstd/bits/format/formatter/bool.hpp
index 336e1b0..e371cec 100644
--- a/libs/kstd/include/kstd/bits/format/formatter/bool.hpp
+++ b/libs/kstd/include/kstd/bits/format/formatter/bool.hpp
@@ -51,11 +51,11 @@ namespace kstd
auto const text = value ? std::string_view{"true"} : std::string_view{"false"};
auto final_width = 0uz;
- if (specifiers.width_mode == bits::format::width_mode::static_value)
+ if (specifiers.mode == bits::format::width_mode::static_value)
{
final_width = specifiers.width_value;
}
- else if (specifiers.width_mode == bits::format::width_mode::dynamic_argument_id)
+ else if (specifiers.mode == bits::format::width_mode::dynamic_argument_id)
{
auto const & arg = context.arg(specifiers.width_value);
final_width = bits::format::extrat_dynamic_width(arg);
diff --git a/libs/kstd/include/kstd/bits/format/formatter/char.hpp b/libs/kstd/include/kstd/bits/format/formatter/char.hpp
new file mode 100644
index 0000000..ddfefe5
--- /dev/null
+++ b/libs/kstd/include/kstd/bits/format/formatter/char.hpp
@@ -0,0 +1,94 @@
+#ifndef KSTD_BITS_FORMAT_FORMATTER_CHAR_HPP
+#define KSTD_BITS_FORMAT_FORMATTER_CHAR_HPP
+
+#include "../context.hpp"
+#include "../error.hpp"
+#include "../formatter.hpp"
+#include "../parse_context.hpp"
+#include "../specifiers.hpp"
+#include "integral.hpp"
+
+#include <iterator>
+
+namespace kstd
+{
+
+ template<>
+ struct formatter<char> : formatter<unsigned char>
+ {
+ bits::format::specifiers specifiers{};
+
+ constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator
+ {
+ specifiers = bits::format::parse_format_specifiers(context);
+
+ auto it = context.begin();
+ auto const end = context.end();
+
+ if (specifiers.alternative_form || specifiers.zero_pad || specifiers.sign != bits::format::sign_mode::none)
+ {
+ bits::format::error("Invalid format specifiers for 'char'");
+ }
+
+ if (it != end && *it != '}')
+ {
+ if (*it == 'c' || *it == 'b' || *it == 'B' || *it == 'd' || *it == 'o' || *it == 'x' || *it == 'X')
+ {
+ specifiers.type = *it;
+ std::advance(it, 1);
+ }
+ else
+ {
+ bits::format::error("Invalid type specifier for char.");
+ }
+ }
+
+ if (it != end && *it != '}')
+ {
+ bits::format::error("Missing terminating '}' in format string.");
+ }
+
+ return it;
+ }
+
+ auto format(char value, format_context & context) const -> void
+ {
+ if (specifiers.type == '\0' || specifiers.type == 'c')
+ {
+ auto final_width = 0uz;
+
+ if (specifiers.mode == bits::format::width_mode::static_value)
+ {
+ final_width = specifiers.width_value;
+ }
+ else if (specifiers.mode == bits::format::width_mode::dynamic_argument_id)
+ {
+ auto const & arg = context.arg(specifiers.width_value);
+ final_width = bits::format::extrat_dynamic_width(arg);
+ }
+
+ auto padding =
+ bits::format::calculate_format_padding(final_width, 1, specifiers.align, bits::format::alignment::left);
+
+ for (auto i = 0uz; i < padding.left; ++i)
+ {
+ context.push(specifiers.fill);
+ }
+
+ context.push(value);
+
+ for (auto i = 0uz; i < padding.right; ++i)
+ {
+ context.push(specifiers.fill);
+ }
+ }
+ else
+ {
+ formatter<unsigned char>::format(static_cast<unsigned char>(value), context);
+ }
+ }
+ };
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/include/kstd/bits/format/formatter/integral.hpp b/libs/kstd/include/kstd/bits/format/formatter/integral.hpp
index 4912a44..e5a234a 100644
--- a/libs/kstd/include/kstd/bits/format/formatter/integral.hpp
+++ b/libs/kstd/include/kstd/bits/format/formatter/integral.hpp
@@ -64,11 +64,11 @@ namespace kstd
auto format(T value, format_context & context) const -> void
{
auto final_width = 0uz;
- if (specifiers.width_mode == bits::format::width_mode::static_value)
+ if (specifiers.mode == bits::format::width_mode::static_value)
{
final_width = specifiers.width_value;
}
- else if (specifiers.width_mode == bits::format::width_mode::dynamic_argument_id)
+ else if (specifiers.mode == bits::format::width_mode::dynamic_argument_id)
{
auto const & arg = context.arg(specifiers.width_value);
final_width = bits::format::extrat_dynamic_width(arg);
diff --git a/libs/kstd/include/kstd/bits/format/output_buffer.hpp b/libs/kstd/include/kstd/bits/format/output_buffer.hpp
new file mode 100644
index 0000000..fd7a2b4
--- /dev/null
+++ b/libs/kstd/include/kstd/bits/format/output_buffer.hpp
@@ -0,0 +1,32 @@
+#ifndef KSTD_BITS_FORMAT_OUTPUT_BUFFER_HPP
+#define KSTD_BITS_FORMAT_OUTPUT_BUFFER_HPP
+
+// IWYU pragma: private, include <kstd/format>
+
+#include <string_view>
+
+namespace kstd::bits::format
+{
+
+ //! An abstract interface for formatted output buffers.
+ //!
+ //! This interface is intended to be use for functions dealing with string formatting, like the print and the format
+ //! family.
+ struct output_buffer
+ {
+ virtual ~output_buffer() = default;
+
+ //! Push a text segment into the buffer.
+ //!
+ //! @param text The text segment to push.
+ virtual auto push(std::string_view text) -> void = 0;
+
+ //! Push a single character into the buffer.
+ //!
+ //! @param character The character to push into the buffer.
+ virtual auto push(char character) -> void = 0;
+ };
+
+} // namespace kstd::bits::format
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/include/kstd/bits/format/specifiers.hpp b/libs/kstd/include/kstd/bits/format/specifiers.hpp
index 9bc66c7..18c6f66 100644
--- a/libs/kstd/include/kstd/bits/format/specifiers.hpp
+++ b/libs/kstd/include/kstd/bits/format/specifiers.hpp
@@ -43,7 +43,7 @@ namespace kstd::bits::format
bool alternative_form{};
bool zero_pad{};
- width_mode width_mode{};
+ width_mode mode{};
std::size_t width_value{};
char type{};
};
@@ -133,7 +133,7 @@ namespace kstd::bits::format
if (it != end && *it == '{')
{
- specs.width_mode = width_mode::dynamic_argument_id;
+ specs.mode = width_mode::dynamic_argument_id;
std::advance(it, 1);
auto argument_id = 0uz;
@@ -160,7 +160,7 @@ namespace kstd::bits::format
}
else if (it != end && *it >= '0' && *it <= '9')
{
- specs.width_mode = width_mode::static_value;
+ specs.mode = width_mode::static_value;
while (it != end && *it >= '0' && *it <= '9')
{
specs.width_value = specs.width_value * 10 + static_cast<std::size_t>(*it - '0');
diff --git a/libs/kstd/include/kstd/bits/format/vformat.hpp b/libs/kstd/include/kstd/bits/format/vformat.hpp
new file mode 100644
index 0000000..4fec7dd
--- /dev/null
+++ b/libs/kstd/include/kstd/bits/format/vformat.hpp
@@ -0,0 +1,99 @@
+#ifndef KSTD_BITS_FORMAT_VFORMAT_HPP
+#define KSTD_BITS_FORMAT_VFORMAT_HPP
+
+// IWYU pragma: private, include <kstd/format>
+
+#include <kstd/bits/format/args.hpp>
+#include <kstd/bits/format/output_buffer.hpp>
+#include <kstd/bits/format/string.hpp>
+#include <kstd/string>
+
+#include <algorithm>
+#include <iterator>
+#include <string_view>
+#include <type_traits>
+
+namespace kstd
+{
+
+ namespace bits::format
+ {
+ //! Format a string with the given arguments into the given buffer.
+ //!
+ //! External implementations of `vprint` may call this function.
+ //!
+ //! @param buffer The buffer to format into.
+ //! @param format The format string to use.
+ //! @param args The arguments for the format string.
+ auto vformat_to(output_buffer & buffer, std::string_view format, format_args args) -> void;
+
+ struct string_writer : output_buffer
+ {
+ auto push(std::string_view text) -> void override;
+ auto push(char character) -> void override;
+
+ auto release() -> string &&;
+
+ private:
+ string m_result{};
+ };
+
+ template<std::output_iterator<char> Output>
+ struct iterator_writer : output_buffer
+ {
+ explicit iterator_writer(Output iterator)
+ : m_output{iterator}
+ {}
+
+ auto iterator() const -> Output
+ {
+ return m_output;
+ }
+
+ auto push(std::string_view text) -> void override
+ {
+ m_output = std::ranges::copy(text, m_output).out;
+ }
+
+ auto push(char character) -> void override
+ {
+ *m_output++ = character;
+ }
+
+ private:
+ Output m_output{};
+ };
+
+ } // namespace bits::format
+
+ //! Format a given string with the provided arguments.
+ //!
+ //! @param format The format string.
+ //! @param args The arguments for the format string.
+ //! @return A new string containing the result of the format operation.
+ template<typename... ArgumentTypes>
+ auto format(format_string<std::type_identity_t<ArgumentTypes>...> format, ArgumentTypes &&... args) -> string
+ {
+ auto buffer = bits::format::string_writer{};
+ bits::format::vformat_to(buffer, format.str_view, make_format_args(std::forward<ArgumentTypes>(args)...).args);
+ return buffer.release();
+ }
+
+ //! Format a given string with the provided arguments.
+ //!
+ //! @param iterator The iterator to write to.
+ //! @param format The format string.
+ //! @param args The arguments for the format string.
+ //! @return An iterator past the last element written.
+ template<std::output_iterator<char> Output, typename... ArgumentTypes>
+ auto format_to(Output iterator, format_string<std::type_identity_t<ArgumentTypes>...> format,
+ ArgumentTypes &&... args) -> Output
+ {
+ auto buffer = bits::format::iterator_writer{iterator};
+ bits::format::vformat_to(buffer, format.str_view, make_format_args(std::forward<ArgumentTypes>(args)...).args);
+ return buffer.iterator();
+ }
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/include/kstd/flat_map b/libs/kstd/include/kstd/flat_map
index 3b13754..f12b1b5 100644
--- a/libs/kstd/include/kstd/flat_map
+++ b/libs/kstd/include/kstd/flat_map
@@ -256,6 +256,33 @@ namespace kstd
};
}
+ //! Try to insert a element for the given key into this map.
+ //!
+ //! This function does nothing if the key is already present.
+ //!
+ //! @param key The key to insert a value for.
+ //! @param args The arguments to use to construct the mapped value.
+ template<typename... Args>
+ auto try_emplace(key_type const & key, Args &&... args) -> std::pair<iterator, bool>
+ {
+ auto found = std::ranges::lower_bound(m_containers.keys, key, m_comparator);
+ if (found != m_containers.keys.cend() && !m_comparator(*found, key) && !m_comparator(key, *found))
+ {
+ return {found, false};
+ }
+
+ auto offset = std::distance(m_containers.keys.cbegin(), found);
+ auto intersertion_point = m_containers.value.begin() + offset;
+
+ auto inserted_key = m_containers.keys.emplace(key);
+ auto inserted_mapped = m_containers.values.emplace(std::forward<Args>(args)...);
+
+ return {
+ iterator{inserted_key, inserted_mapped},
+ true
+ };
+ }
+
//! Find an element with an equivalent key.
//!
//! @param key The key to look up.
diff --git a/libs/kstd/include/kstd/format b/libs/kstd/include/kstd/format
index adee5f8..047ea5c 100644
--- a/libs/kstd/include/kstd/format
+++ b/libs/kstd/include/kstd/format
@@ -7,13 +7,16 @@
#include "bits/format/formatter.hpp" // IWYU pragma: export
#include "bits/format/formatter/bool.hpp" // IWYU pragma: export
#include "bits/format/formatter/byte.hpp" // IWYU pragma: export
+#include "bits/format/formatter/char.hpp" // IWYU pragma: export
#include "bits/format/formatter/cstring.hpp" // IWYU pragma: export
#include "bits/format/formatter/integral.hpp" // IWYU pragma: export
#include "bits/format/formatter/ordering.hpp" // IWYU pragma: export
#include "bits/format/formatter/pointer.hpp" // IWYU pragma: export
#include "bits/format/formatter/range.hpp" // IWYU pragma: export
#include "bits/format/formatter/string_view.hpp" // IWYU pragma: export
+#include "bits/format/output_buffer.hpp" // IWYU pragma: export
#include "bits/format/parse_context.hpp" // IWYU pragma: export
#include "bits/format/string.hpp" // IWYU pragma: export
+#include "bits/format/vformat.hpp" // IWYU pragma: export
#endif \ No newline at end of file
diff --git a/libs/kstd/include/kstd/string b/libs/kstd/include/kstd/string
index 4ce19ce..58c4a08 100644
--- a/libs/kstd/include/kstd/string
+++ b/libs/kstd/include/kstd/string
@@ -347,6 +347,26 @@ namespace kstd
return !(lhs == rhs);
}
+ [[nodiscard]] constexpr auto inline operator==(string const & lhs, char const * rhs) -> bool
+ {
+ return lhs.view() == std::string_view{rhs};
+ }
+
+ [[nodiscard]] constexpr auto inline operator!=(string const & lhs, char const * rhs) -> bool
+ {
+ return !(lhs == rhs);
+ }
+
+ [[nodiscard]] constexpr auto inline operator==(char const * lhs, string const & rhs) -> bool
+ {
+ return std::string_view{lhs} == rhs.view();
+ }
+
+ [[nodiscard]] constexpr auto inline operator!=(char const * lhs, string const & rhs) -> bool
+ {
+ return !(lhs == rhs);
+ }
+
template<>
struct formatter<string> : formatter<std::string_view>
{
diff --git a/libs/kstd/include/kstd/vector b/libs/kstd/include/kstd/vector
index e51cbac..e1b8b38 100644
--- a/libs/kstd/include/kstd/vector
+++ b/libs/kstd/include/kstd/vector
@@ -111,7 +111,7 @@ namespace kstd
allocator_type const & allocator =
allocator_type{}) noexcept(std::is_nothrow_copy_constructible_v<allocator_type>)
: m_allocator{allocator}
- , m_size{std::ranges::distance(first, last)}
+ , m_size{static_cast<std::size_t>(std::ranges::distance(first, last))}
, m_capacity{m_size}
, m_data{allocate_n(m_capacity)}
{
@@ -728,6 +728,45 @@ namespace kstd
return begin() + prefix_size;
}
+ template<typename... Args>
+ constexpr auto emplace(const_iterator position, Args &&... args) -> iterator
+ {
+ auto prefix_size = std::ranges::distance(begin(), position);
+ auto suffix_size = std::ranges::distance(position, end());
+
+ if (position == end())
+ {
+ emplace_back(std::forward<Args>(args)...);
+ return begin() + prefix_size;
+ }
+
+ if (m_capacity == m_size)
+ {
+ auto new_capacity = m_capacity == 0 ? 1 : m_capacity * 2;
+ auto new_data = allocate_n(new_capacity);
+ auto old_size = size();
+ std::allocator_traits<allocator_type>::construct(m_allocator, new_data + prefix_size,
+ std::forward<Args>(args)...);
+ uninitialized_move_with_allocator(begin(), new_data, prefix_size);
+ uninitialized_move_with_allocator(begin() + prefix_size, new_data + prefix_size + 1, suffix_size);
+ destroy_n(begin(), old_size);
+ deallocate();
+ m_data = new_data;
+ m_capacity = new_capacity;
+ m_size = old_size;
+ }
+ else
+ {
+ auto insert_position = begin() + prefix_size;
+ std::allocator_traits<allocator_type>::construct(m_allocator, end(), std::move(*(end() - 1)));
+ std::ranges::move_backward(insert_position, end() - 1, end());
+ *insert_position = value_type{std::forward<Args>(args)...};
+ }
+
+ ++m_size;
+ return begin() + prefix_size;
+ }
+
//! Erase an element at a given position.
//!
//! @note This function will panic if position == end()
diff --git a/libs/kstd/src/vformat.cpp b/libs/kstd/src/vformat.cpp
new file mode 100644
index 0000000..b7c5121
--- /dev/null
+++ b/libs/kstd/src/vformat.cpp
@@ -0,0 +1,209 @@
+#include <kstd/format>
+#include <kstd/string>
+
+#include <cstddef>
+#include <iterator>
+#include <string_view>
+#include <utility>
+
+namespace kstd::bits::format
+{
+
+ auto vformat_to(output_buffer & buffer, std::string_view format, format_args args) -> void
+ {
+ auto context = kstd::format_context{buffer, args};
+ auto parse_context = kstd::format_parse_context{format, args.size()};
+
+ auto it = parse_context.begin();
+ auto end = parse_context.end();
+
+ while (it != end)
+ {
+ if (*it != '{' && *it != '}')
+ {
+ auto start = it;
+ while (it != end && *it != '{' && *it != '}')
+ {
+ std::advance(it, 1);
+ }
+ parse_context.advance_to(it);
+ context.push(std::string_view(start, it - start));
+ continue;
+ }
+
+ if (*it == '{')
+ {
+ std::advance(it, 1);
+ if (it != end && *it == '{')
+ {
+ context.push('{');
+ std::advance(it, 1);
+ parse_context.advance_to(it);
+ continue;
+ }
+
+ parse_context.advance_to(it);
+ auto index = 0uz;
+
+ if (it != end && *it >= '0' && *it <= '9')
+ {
+ while (it != end && *it >= '0' && *it <= '9')
+ {
+ index = index * 10 + static_cast<std::size_t>(*it - '0');
+ std::advance(it, 1);
+ }
+ parse_context.check_arg_id(index);
+ }
+ else
+ {
+ index = parse_context.next_arg_id();
+ }
+
+ if (it != end && *it == ':')
+ {
+ std::advance(it, 1);
+ }
+
+ parse_context.advance_to(it);
+
+ if (index < args.size())
+ {
+ auto const & arg = args[index];
+ switch (arg.type)
+ {
+ case kstd::bits::format::arg_type::boolean:
+ {
+ auto fmt = kstd::formatter<bool>{};
+ auto const parsed = fmt.parse(parse_context);
+ parse_context.advance_to(parsed);
+ fmt.format(arg.value.boolean, context);
+ break;
+ }
+ case kstd::bits::format::arg_type::character:
+ {
+ auto fmt = kstd::formatter<char>{};
+ auto const parsed = fmt.parse(parse_context);
+ parse_context.advance_to(parsed);
+ fmt.format(arg.value.character, context);
+ break;
+ }
+ case kstd::bits::format::arg_type::integer:
+ {
+ auto fmt = kstd::formatter<long long>{};
+ auto const parsed = fmt.parse(parse_context);
+ parse_context.advance_to(parsed);
+ fmt.format(arg.value.integer, context);
+ break;
+ }
+ case kstd::bits::format::arg_type::unsigned_integer:
+ {
+ auto fmt = kstd::formatter<unsigned long long>{};
+ auto const parsed = fmt.parse(parse_context);
+ parse_context.advance_to(parsed);
+ fmt.format(arg.value.unsigned_integer, context);
+ break;
+ }
+ case kstd::bits::format::arg_type::string_view:
+ {
+ auto fmt = kstd::formatter<std::string_view>{};
+ auto const parsed = fmt.parse(parse_context);
+ parse_context.advance_to(parsed);
+ fmt.format(arg.value.string_view, context);
+ break;
+ }
+ case kstd::bits::format::arg_type::c_string:
+ {
+ auto fmt = kstd::formatter<char const *>{};
+ auto const parsed = fmt.parse(parse_context);
+ parse_context.advance_to(parsed);
+ fmt.format(arg.value.c_string, context);
+ break;
+ }
+ case kstd::bits::format::arg_type::pointer:
+ {
+ auto fmt = kstd::formatter<void const *>{};
+ auto const parsed = fmt.parse(parse_context);
+ parse_context.advance_to(parsed);
+ fmt.format(arg.value.pointer, context);
+ break;
+ }
+ case kstd::bits::format::arg_type::user_defined:
+ {
+ if (arg.value.user_defined.format)
+ {
+ arg.value.user_defined.format(arg.value.user_defined.pointer, parse_context, context);
+ }
+ else
+ {
+ context.push("{?}");
+ }
+ break;
+ }
+ default:
+ {
+ context.push("{fmt-err: unknown-type}");
+ break;
+ }
+ }
+ }
+ else
+ {
+ context.push("{fmt-err: bound}");
+ }
+
+ it = parse_context.begin();
+
+ if (it != end && *it == '}')
+ {
+ std::advance(it, 1);
+ parse_context.advance_to(it);
+ }
+ else
+ {
+ context.push("{fmt-err: unconsumed}");
+ while (it != end && *it != '}')
+ {
+ std::advance(it, 1);
+ }
+
+ if (it != end)
+ {
+ std::advance(it, 1);
+ parse_context.advance_to(it);
+ }
+ }
+ }
+ else if (*it == '}')
+ {
+ std::advance(it, 1);
+ if (it != end && *it == '}')
+ {
+ context.push('}');
+ std::advance(it, 1);
+ parse_context.advance_to(it);
+ }
+ else
+ {
+ context.push("{fmt-err: unescaped}");
+ parse_context.advance_to(it);
+ }
+ }
+ }
+ }
+
+ auto string_writer::push(std::string_view text) -> void
+ {
+ m_result.append(text);
+ }
+
+ auto string_writer::push(char character) -> void
+ {
+ m_result.push_back(character);
+ }
+
+ auto string_writer::release() -> string &&
+ {
+ return std::move(m_result);
+ }
+
+} // namespace kstd::bits::format \ No newline at end of file
diff --git a/libs/kstd/tests/src/format.cpp b/libs/kstd/tests/src/format.cpp
new file mode 100644
index 0000000..73c8102
--- /dev/null
+++ b/libs/kstd/tests/src/format.cpp
@@ -0,0 +1,116 @@
+#include <kstd/flat_map>
+#include <kstd/format>
+#include <kstd/tests/os_panic.hpp>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <iterator>
+#include <sstream>
+
+SCENARIO("Formatting to a new string", "[format]")
+{
+ GIVEN("a format string without any placeholders")
+ {
+ auto const & fmt = "This is a test";
+
+ WHEN("calling format with without any arguments.")
+ {
+ auto result = kstd::format(fmt);
+
+ THEN("the result is the unmodified string")
+ {
+ REQUIRE(result == "This is a test");
+ }
+ }
+
+ WHEN("calling format with additional arguments")
+ {
+ auto result = kstd::format(fmt, 1, 2, 3);
+
+ THEN("the result is the unmodified string")
+ {
+ REQUIRE(result == "This is a test");
+ }
+ }
+ }
+
+ GIVEN("a format string with placeholders")
+ {
+ auto const & fmt = "Here are some placeholders: {} {} {}";
+
+ WHEN("calling format with the same number of arguments as there are placeholders")
+ {
+ auto result = kstd::format(fmt, 1, true, 'a');
+
+ THEN("the result is the formatted string")
+ {
+ REQUIRE(result == "Here are some placeholders: 1 true a");
+ }
+ }
+
+ WHEN("calling format with too many arguments")
+ {
+ auto result = kstd::format(fmt, 2, false, 'b', 4, 5, 6);
+
+ THEN("the result is the formatted string")
+ {
+ REQUIRE(result == "Here are some placeholders: 2 false b");
+ }
+ }
+ }
+}
+
+SCENARIO("Formatting to an output iterator", "[format]")
+{
+ auto buffer = std::ostringstream{};
+
+ GIVEN("a format string without any placeholders")
+ {
+ auto const & fmt = "This is a test";
+
+ WHEN("calling format with without any arguments.")
+ {
+ kstd::format_to(std::ostream_iterator<char>{buffer}, fmt);
+
+ THEN("the unmodified string is written to the iterator")
+ {
+ REQUIRE(buffer.str() == "This is a test");
+ }
+ }
+
+ WHEN("calling format with additional arguments")
+ {
+ kstd::format_to(std::ostream_iterator<char>{buffer}, fmt, 1, 2, 3);
+
+ THEN("the unmodified string is written to the iterator")
+ {
+ REQUIRE(buffer.str() == "This is a test");
+ }
+ }
+ }
+
+ GIVEN("a format string with placeholders")
+ {
+ auto const & fmt = "Here are some placeholders: {} {} {}";
+
+ WHEN("calling format with the same number of arguments as there are placeholders")
+ {
+ kstd::format_to(std::ostream_iterator<char>{buffer}, fmt, 1, true, -100);
+
+ THEN("the formatted string is written to the iterator")
+ {
+ REQUIRE(buffer.str() == "Here are some placeholders: 1 true -100");
+ }
+ }
+
+ WHEN("calling format with too many arguments")
+ {
+ kstd::format_to(std::ostream_iterator<char>{buffer}, fmt, 2, false, -200, 4, 5, 6);
+
+ THEN("the formatted string is written to the iterator")
+ {
+ REQUIRE(buffer.str() == "Here are some placeholders: 2 false -200");
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/kstd/tests/src/vector.cpp b/libs/kstd/tests/src/vector.cpp
index 81bf32f..5faaaff 100644
--- a/libs/kstd/tests/src/vector.cpp
+++ b/libs/kstd/tests/src/vector.cpp
@@ -463,6 +463,33 @@ SCENARIO("Vector modifiers", "[vector]")
}
}
+ WHEN("emplace is called with the end iterator and constructor arguments")
+ {
+ v.emplace(v.end(), 20);
+
+ THEN("the element is appended and the size and capacity increase")
+ {
+ REQUIRE(v.size() == 1);
+ REQUIRE(v.capacity() >= 1);
+ REQUIRE(v.back() == 20);
+ }
+ }
+
+ WHEN("emplace is called while capacity is sufficient")
+ {
+ v.reserve(10);
+ auto const capacity = v.capacity();
+
+ v.emplace(v.end(), 20);
+
+ THEN("the element is appended without reallocation")
+ {
+ REQUIRE(v.size() == 1);
+ REQUIRE(v.capacity() == capacity);
+ REQUIRE(v.back() == 20);
+ }
+ }
+
WHEN("inserting an element")
{
auto it = v.insert(v.cbegin(), 42);
@@ -526,6 +553,24 @@ SCENARIO("Vector modifiers", "[vector]")
}
}
+ WHEN("emplace is called with an iterator and constructor arguments")
+ {
+ auto it = v.emplace(v.begin() + 2, 25);
+
+ THEN("the element is inserted and the size and capacity increase")
+ {
+ REQUIRE(v.size() == 4);
+ REQUIRE(v.capacity() >= initial_capacity);
+ REQUIRE(v.at(2) == 25);
+ }
+
+ THEN("the returned iterator points to the inserted element")
+ {
+ REQUIRE(it == v.cbegin() + 2);
+ REQUIRE(*it == 25);
+ }
+ }
+
WHEN("push_back is called with a reference to an internal element")
{
v.shrink_to_fit();