From 91d54c58d53103ee7c934aad83b4761ddf163efd Mon Sep 17 00:00:00 2001 From: "M. George Hansen" Date: Sat, 7 Jun 2025 14:17:56 -0600 Subject: [PATCH] Initial commit --- .clang-format | 22 +++ .gitignore | 14 ++ CMakeLists.txt | 94 +++++++++++ README.md | 12 ++ src/bedrock.cppm | 7 + src/cli.cppm | 3 + src/cli/program.cppm | 46 ++++++ src/collections.cppm | 4 + src/collections/hashmap.cppm | 87 ++++++++++ src/collections/sequence.cppm | 53 ++++++ src/foundation.cppm | 14 ++ src/foundation/buffer.cppm | 193 ++++++++++++++++++++++ src/foundation/buffer_view.cppm | 131 +++++++++++++++ src/foundation/byte.cppm | 62 +++++++ src/foundation/bytes_iterator.cppm | 90 +++++++++++ src/foundation/iterators/adapter.cppm | 20 +++ src/foundation/iterators/enumerate.cppm | 105 ++++++++++++ src/foundation/iterators/reverse.cppm | 101 ++++++++++++ src/foundation/optional.cppm | 178 ++++++++++++++++++++ src/foundation/result.cppm | 205 ++++++++++++++++++++++++ src/foundation/string.cppm | 130 +++++++++++++++ src/foundation/string_buffer.cppm | 79 +++++++++ src/foundation/tuple.cppm | 33 ++++ src/foundation/utility.cppm | 19 +++ src/io.cppm | 3 + src/io/channels.cppm | 42 +++++ src/numbers.cppm | 31 ++++ src/semantics.cppm | 13 ++ src/semantics/callable.cppm | 13 ++ src/semantics/copyable.cppm | 13 ++ src/semantics/defaultable.cppm | 12 ++ src/semantics/equivalence.cppm | 25 +++ src/semantics/floating_point.cppm | 28 ++++ src/semantics/integral.cppm | 58 +++++++ src/semantics/iterable.cppm | 44 +++++ src/semantics/moveable.cppm | 25 +++ src/semantics/ordered.cppm | 47 ++++++ src/semantics/referenceable.cppm | 51 ++++++ src/semantics/utility.cppm | 18 +++ src/test.cppm | 3 + src/test/api.cppm | 67 ++++++++ src/test/runner.cppm | 17 ++ 42 files changed, 2212 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 src/bedrock.cppm create mode 100644 src/cli.cppm create mode 100644 src/cli/program.cppm create mode 100644 src/collections.cppm create mode 100644 src/collections/hashmap.cppm create mode 100644 src/collections/sequence.cppm create mode 100644 src/foundation.cppm create mode 100644 src/foundation/buffer.cppm create mode 100644 src/foundation/buffer_view.cppm create mode 100644 src/foundation/byte.cppm create mode 100644 src/foundation/bytes_iterator.cppm create mode 100644 src/foundation/iterators/adapter.cppm create mode 100644 src/foundation/iterators/enumerate.cppm create mode 100644 src/foundation/iterators/reverse.cppm create mode 100644 src/foundation/optional.cppm create mode 100644 src/foundation/result.cppm create mode 100644 src/foundation/string.cppm create mode 100644 src/foundation/string_buffer.cppm create mode 100644 src/foundation/tuple.cppm create mode 100644 src/foundation/utility.cppm create mode 100644 src/io.cppm create mode 100644 src/io/channels.cppm create mode 100644 src/numbers.cppm create mode 100644 src/semantics.cppm create mode 100644 src/semantics/callable.cppm create mode 100644 src/semantics/copyable.cppm create mode 100644 src/semantics/defaultable.cppm create mode 100644 src/semantics/equivalence.cppm create mode 100644 src/semantics/floating_point.cppm create mode 100644 src/semantics/integral.cppm create mode 100644 src/semantics/iterable.cppm create mode 100644 src/semantics/moveable.cppm create mode 100644 src/semantics/ordered.cppm create mode 100644 src/semantics/referenceable.cppm create mode 100644 src/semantics/utility.cppm create mode 100644 src/test.cppm create mode 100644 src/test/api.cppm create mode 100644 src/test/runner.cppm diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..e8db281 --- /dev/null +++ b/.clang-format @@ -0,0 +1,22 @@ +--- +BasedOnStyle: Google +Language: Cpp +Standard: c++20 +TabWidth: 4 +IndentWidth: 4 +ColumnLimit: 120 +InsertNewlineAtEOF: True +AllowShortBlocksOnASingleLine: Empty +AllowShortCaseLabelsOnASingleLine: False +AllowShortCompoundRequirementOnASingleLine: True +AllowShortEnumsOnASingleLine: True +AllowShortFunctionsOnASingleLine: Empty +UseTab: Always +FixNamespaceComments: True +BreakAfterAttributes: Always +BreakTemplateDeclarations: Yes +PackConstructorInitializers: NextLineOnly +SeparateDefinitionBlocks: Always +ReflowComments: Always +RequiresExpressionIndentation: OuterScope +... diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..765fb11 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +* +!/.gitignore + +!/README.md +!/CMakeLists.txt +!/.clang-format + +!/src/ +!/src/**/ +!/src/**/*.cppm + +!/test/ +!/test/**/ +!/test/**/*.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e44bff6 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,94 @@ +cmake_minimum_required(VERSION 3.28) +project(bedrock VERSION 0.1 LANGUAGES CXX) + +find_program( + CLANG_TIDY_COMMAND + NAMES + clang-tidy + HINTS + /opt/homebrew/opt/llvm/bin/ + NO_CACHE + REQUIRED +) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED YES) +set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +add_compile_options( + -Weverything + -Werror + -Wno-c++98-compat + -Wno-c++98-compat-pedantic + -Wno-poison-system-directories + -Wno-explicit-specialization-storage-class + -Wno-unsafe-buffer-usage + -Wno-padded + -Wno-global-constructors + -Wno-exit-time-destructors + -fno-exceptions + -fcheck-new +) + +file(GLOB_RECURSE BEDROCK_NUMBERS "src/numbers.cppm") +file(GLOB_RECURSE BEDROCK_FOUNDATION "src/foundation/*.cppm" "src/foundation.cppm") +file(GLOB_RECURSE BEDROCK_SEMANTICS "src/semantics/*.cppm" "src/semantics.cppm") +file(GLOB_RECURSE BEDROCK_COLLECTIONS "src/collections/*.cppm" "src/collections.cppm") +file(GLOB_RECURSE BEDROCK_IO "src/io/*.cppm" "src/io.cppm") +file(GLOB_RECURSE BEDROCK_CLI "src/cli/*.cppm" "src/cli.cppm") +file(GLOB_RECURSE BEDROCK_TEST "src/test/*.cppm" "src/test.cppm") +add_library(bedrock) +target_sources(bedrock + PUBLIC + FILE_SET CXX_MODULES FILES + ${BEDROCK_NUMBERS} + ${BEDROCK_SEMANTICS} + ${BEDROCK_FOUNDATION} + ${BEDROCK_COLLECTIONS} + ${BEDROCK_IO} + ${BEDROCK_CLI} + ${BEDROCK_TEST} + src/bedrock.cppm +) +set_target_properties(bedrock PROPERTIES + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR} + CXX_CLANG_TIDY "${CLANG_TIDY_COMMAND}" +) + +file(GLOB_RECURSE BEDROCK_TEST_SRCS "src/*.test.cpp") +add_executable(bedrock-tests ${BEDROCK_TEST_SRCS}) +target_link_libraries(bedrock-tests PUBLIC bedrock) + +#include(FetchContent) +# +#fetchcontent_declare( +# Catch2 +# GIT_REPOSITORY https://github.com/catchorg/Catch2.git +# GIT_TAG v3.4.0 +# SYSTEM +#) +#fetchcontent_makeavailable(Catch2) +#list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras) +# +#include(CTest) +#include(Catch) +# +#file(GLOB_RECURSE BEDROCK_TESTS "src/*.test.cpp") +#add_executable(bedrock-tests +# ${BEDROCK_TESTS} +#) +#target_compile_options(bedrock-tests PUBLIC +# -Weverything +# -Werror +# -Wno-c++98-compat +# -Wno-c++98-compat-pedantic +# -Wno-poison-system-directories +# -Wno-unused-member-function +# -Wno-explicit-specialization-storage-class +#) +#target_link_libraries(bedrock-tests +# PUBLIC bedrock +# PRIVATE Catch2::Catch2WithMain +#) +#catch_discover_tests(bedrock-tests) diff --git a/README.md b/README.md new file mode 100644 index 0000000..cc07261 --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +# Bedrock: A Modern C++ Stdlib Alternative + +The C++ stdlib sacrifices readability and ease of use for flexibility and performance. Bedrock takes a different approach: it stresses ease of use and minimalistic abstractions for common use cases, resulting in code that is easier to read, understand, and test. + +Explicit goals for Bedrock are: + +1. **No implicit copies:** move semantics are given first-party support in Bedrock, and copies should never be done implicitly except for extremely simple structures where the performance cost is minimal. +2. **Easy to understand semantics:** concepts are heavily used throughout Bedrock to enforce high-level semantics, such as "Moveable", "Iterable", etc., and those concepts encompass all related behavior and best practices (e.g. "Movable" implies both move-constructable and move-assignable). +3. **Modern data structures & abstractions:** includes optional types, result types, and a robust iterator library. +4. **Everything is a buffer or view:** C++ is all about giving developers low-level access to memory, and Bedrock doesn't try to hide the fact that all data structures represent some kind of memory view or buffer. + +Bedrock is currently in a HIGHLY experimental state and is being built by someone with limited C++ and stdlib experience, so USE AT YOUR OWN RISK! Changes in the 0.x version range can and WILL break existing code. diff --git a/src/bedrock.cppm b/src/bedrock.cppm new file mode 100644 index 0000000..c717b99 --- /dev/null +++ b/src/bedrock.cppm @@ -0,0 +1,7 @@ +export module bedrock; + +export import bedrock.numbers; +export import bedrock.semantics; +export import bedrock.foundation; +export import bedrock.cli; +export import bedrock.io; diff --git a/src/cli.cppm b/src/cli.cppm new file mode 100644 index 0000000..10de372 --- /dev/null +++ b/src/cli.cppm @@ -0,0 +1,3 @@ +export module bedrock.cli; + +export import :program; diff --git a/src/cli/program.cppm b/src/cli/program.cppm new file mode 100644 index 0000000..f3e9019 --- /dev/null +++ b/src/cli/program.cppm @@ -0,0 +1,46 @@ +export module bedrock.cli:program; + +import bedrock.semantics; +import bedrock.foundation; +import bedrock.collections; +import bedrock.io; + +namespace br { + +export struct ProgramArg final { + String name; + String short_option; + bool positional; +}; + +export struct ProgramOptions final { + Sequence args; +}; + +export template +class ProgramArgs final { + private: + Tuple m_args; +}; + +export class Program final { + private: + String m_program_name; + Sequence m_args; + + public: + Program(const String program_name, ProgramOptions&& options) + : m_program_name(program_name), m_args(move(options.args)) {} + + template , const Hashmap&> C> + auto run(int, char**, C op) -> int { + auto args = Hashmap::withCapacity(0); + auto result = op(args); + if (result.isFailure()) { + return 1; + } + return 0; + } +}; + +} // namespace br diff --git a/src/collections.cppm b/src/collections.cppm new file mode 100644 index 0000000..a3d62c1 --- /dev/null +++ b/src/collections.cppm @@ -0,0 +1,4 @@ +export module bedrock.collections; + +export import :hashmap; +export import :sequence; diff --git a/src/collections/hashmap.cppm b/src/collections/hashmap.cppm new file mode 100644 index 0000000..e3b15d4 --- /dev/null +++ b/src/collections/hashmap.cppm @@ -0,0 +1,87 @@ +export module bedrock.collections:hashmap; + +import bedrock.foundation; +import :sequence; + +namespace br { + +inline namespace { + +constexpr float32 max_load_factor = 0.7F; +constexpr usize fnv_offset_basis = sizeof(usize) == 8 ? 0xcbf29ce484222325 : 0x811c9dc5; +constexpr usize fnv_prime = sizeof(usize) == 8 ? 0x100000001b3 : 0x1000193; + +} // namespace + +inline auto hashFnv1a(const String& key) noexcept -> usize { + usize hash = fnv_offset_basis; + for (auto byte : static_cast(key).bytes()) { + hash ^= static_cast(byte); + hash *= fnv_prime; + } + return hash; +}; + +export template +class Hashmap final { + private: + struct Slot { + Key key; + Optional value; + + [[nodiscard]] + auto isUnoccupied() const noexcept -> bool { + return key.isEmpty(); + } + }; + + Sequence m_slots; + + explicit Hashmap(usize capacity) + : m_slots(Sequence::withCapacity(capacity * sizeof(Slot))) {} + + [[nodiscard]] + auto loadFactor() -> float32 { + return static_cast(m_slots.usedCapacity()) / m_slots.capacity(); + } + + [[nodiscard]] + auto findSlot(const Key& key) const noexcept -> Optional { + auto index = hashFnv1a(key) % m_slots.capacity(); + auto slot = m_slots.lookup(index); + auto iteration = 1; + while (slot.isPresent()) { + slot = m_slots[index + (iteration * iteration)]; + ++iteration; + } + } + + public: + static auto withCapacity(usize capacity) noexcept -> Hashmap { + return Hashmap(capacity); + } + + [[nodiscard]] + auto lookup(const Key& key) const noexcept -> Optional { + return findSlot(key).map([](Slot& slot) -> Value { return slot.value; }); + } + + [[nodiscard]] + auto insert(const Key& key, const Value&& value) noexcept -> bool { + if (loadFactor() >= max_load_factor) { + return false; + } + findSlot(key).tap([=](Slot slot) { + slot.key = key; + slot.value = value; + }); + } + + auto remove(const Key& key) -> void { + auto slot = findSlot(key); + slot.key = ""; + slot.value = Optional::absent(); + } +}; + +} // namespace br diff --git a/src/collections/sequence.cppm b/src/collections/sequence.cppm new file mode 100644 index 0000000..e059219 --- /dev/null +++ b/src/collections/sequence.cppm @@ -0,0 +1,53 @@ +export module bedrock.collections:sequence; + +import bedrock.numbers; +import bedrock.foundation; + +namespace br { + +export template +class Sequence final { + private: + Buffer m_items; + + Sequence(usize capacity) + : m_items(Buffer::withCapacity(capacity * sizeof(T))) {} + + public: + static auto withCapacity(usize capacity) noexcept -> Sequence { + return {capacity}; + } + + template + Sequence(Args&&... items) + : m_items(Buffer::withCapacity(sizeof...(items) * sizeof(T))) { + (push(forward(items)), ...); + } + + auto push(T&& item) noexcept -> bool { + return m_items.write(forward(item), m_items.usedSize()); + } + + [[nodiscard]] + auto operator[](usize index) const noexcept -> Optional { + return m_items.slice(sizeof(T), index * sizeof(T)).map([](BufferView view) -> const T& { + return view.reinterpret(); + }); + } + + auto insert(usize index, T&& value) const noexcept -> bool { + return m_items.write(value, index * sizeof(T)); + } + + [[nodiscard]] + auto size() const noexcept -> usize { + return m_items.usedSize() / sizeof(T); + } + + [[nodiscard]] + auto capacity() const noexcept -> usize { + return m_items.capacity() / sizeof(T); + } +}; + +} // namespace br diff --git a/src/foundation.cppm b/src/foundation.cppm new file mode 100644 index 0000000..562e96b --- /dev/null +++ b/src/foundation.cppm @@ -0,0 +1,14 @@ +export module bedrock.foundation; + +export import :buffer_view; +export import :buffer; +export import :byte; +export import :bytes_iterator; +export import :optional; +export import :result; +export import :string_buffer; +export import :string; +export import :tuple; +export import :iterators.adapter; +export import :iterators.enumerate; +export import :iterators.reverse; diff --git a/src/foundation/buffer.cppm b/src/foundation/buffer.cppm new file mode 100644 index 0000000..3fa8da8 --- /dev/null +++ b/src/foundation/buffer.cppm @@ -0,0 +1,193 @@ +export module bedrock.foundation:buffer; + +import bedrock.numbers; +import bedrock.semantics; + +import :byte; +import :bytes_iterator; +import :iterators.enumerate; +import :buffer_view; +import :utility; + +namespace br { + +export class Buffer final { + private: + byte* m_data; + usize m_capacity; + usize m_used; + + // ==================================================================== + // Helpers + // ==================================================================== + Buffer(const byte* data, usize size, usize capacity_hint) + : m_data(new byte[size]), m_capacity(nearestPowerOf2(capacity_hint)), m_used(size) { + for (usize i = 0; i < m_used; i++) { + m_data[i] = data == nullptr ? byte{} : data[i]; + } + } + + [[nodiscard]] + auto resize(usize size_hint) noexcept -> bool { + const usize size = nearestPowerOf2(size_hint); + auto* new_buffer = new byte[size]; + if (new_buffer == nullptr) { + return false; + } + + for (auto [byte, i] : bytes() | enumerate()) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + new_buffer[i] = byte; + } + delete[] m_data; + m_data = new_buffer; + + return true; + } + + public: + // ==================================================================== + // Static Constructors + // ==================================================================== + [[nodiscard]] + static auto withCapacity(usize capacity) noexcept -> Buffer { + return {nullptr, capacity, 0}; + } + + [[nodiscard]] + static auto copyFrom(const BufferView& view) noexcept -> Buffer { + return {view.m_data, view.m_size, view.m_size}; + } + + template + [[nodiscard]] + auto write(T&& object, usize start_at = 0) noexcept -> bool { + if (start_at + sizeof(T) > m_capacity) { + return false; + } + + auto* bytes = reinterpret_cast(&object); + for (usize i = 0; i < sizeof(T); ++i) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + m_data[start_at + i] = bytes[i]; + } + m_used = start_at + sizeof(T); + + return true; + } + + template <> + [[nodiscard]] + auto write(BufferView&& view, usize start_at) noexcept -> bool { + if (start_at + view.size() > m_capacity) { + return false; + } + + for (auto [byte, i] : view.bytes() | enumerate()) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + m_data[start_at + i] = byte; + } + m_used = start_at + view.size(); + + return true; + } + + [[nodiscard]] + auto slice(usize size, usize offset = 0) const noexcept -> Optional { + return static_cast(*this).slice(size, offset); + } + + // ==================================================================== + // Introspection + // ==================================================================== + [[nodiscard]] + auto usedSize() const noexcept -> usize { + return m_used; + } + + [[nodiscard]] + auto capacity() const noexcept -> usize { + return m_capacity; + } + + [[nodiscard]] + auto copy() const noexcept -> Buffer { + return {m_data, m_used, m_capacity}; + } + + // ==================================================================== + // Conversions + // ==================================================================== + [[nodiscard]] + auto operator->() const noexcept -> BufferView { + return {m_data, m_capacity}; + } + + operator BufferView() const noexcept { + return {m_data, m_used}; + } + + // ==================================================================== + // Iterators + // ==================================================================== + [[nodiscard]] + auto bytes() const noexcept -> BytesIterator { + return {m_data, 0, m_used}; + } + + // ==================================================================== + // Moveable + // ==================================================================== + Buffer(Buffer&& other) noexcept + : m_data(move(other.m_data)), m_capacity(other.m_capacity), m_used(other.m_used) {} + + auto operator=(Buffer&& other) noexcept -> Buffer& { + if (&other == this) { + return *this; + } + + delete[] m_data; + m_capacity = other.m_capacity; + m_data = other.m_data; + + return *this; + } + + ~Buffer() noexcept { + delete[] m_data; + } + + // ==================================================================== + // !Copyable + // ==================================================================== + Buffer(const Buffer&) = delete; + + auto operator=(const Buffer&) -> Buffer& = delete; + + // ==================================================================== + // PartialEquivalence + // ==================================================================== + [[nodiscard]] + auto operator==(const Buffer& other) const noexcept -> bool { + if (m_capacity != other.m_capacity || m_used != other.m_used) { + return false; + } + for (usize i = 0; i < m_capacity; ++i) { + if (m_data[i] != other.m_data[i]) { + return false; + } + } + return true; + } + + [[nodiscard]] + auto operator!=(const Buffer& other) const noexcept -> bool { + return !(*this == other); + } +}; + +static_assert(Moveable); +static_assert(!Copyable); +static_assert(PartialEquivalence); + +} // namespace br diff --git a/src/foundation/buffer_view.cppm b/src/foundation/buffer_view.cppm new file mode 100644 index 0000000..db669fa --- /dev/null +++ b/src/foundation/buffer_view.cppm @@ -0,0 +1,131 @@ +export module bedrock.foundation:buffer_view; + +import bedrock.semantics; +import :byte; +import :optional; +import :bytes_iterator; + +namespace br { + +export class BufferView final { + private: + byte* m_data; + usize m_size; + + public: + friend class Buffer; + + BufferView(const byte* data, usize size) + : m_data(const_cast(data)), m_size(size) {} + + template + constexpr BufferView(byte const (&bytes)[Size]) + : BufferView(static_cast(bytes), Size) {} + + [[nodiscard]] + auto bytes() const noexcept -> BytesIterator { + return {m_data, m_size, 0}; + } + + [[nodiscard]] + auto ptr() const noexcept -> const byte* { + return m_data; + } + + // ==================================================================== + // Introspection + // ==================================================================== + [[nodiscard]] + auto size() const noexcept -> usize { + return m_size; + } + + auto operator[](usize index) const noexcept -> Optional { + if (index >= m_size) { + return Optional::absent(); + } + return Optional::present(m_data[index]); + } + + [[nodiscard]] + auto slice(usize size, usize offset = 0) const noexcept -> Optional { + if (offset + size >= m_size) { + return Optional::absent(); + } + return Optional::present({m_data + offset, size}); + } + + template + [[nodiscard]] + auto reinterpret() const noexcept -> Optional { + if (sizeof(T) >= m_size) { + return Optional::absent(); + } + return Optional::present(reinterpret_cast(m_data)); + } + + // ==================================================================== + // Moveable + // ==================================================================== + BufferView(BufferView&& other) noexcept + : m_data(other.m_data), m_size(other.m_size) {} + + auto operator=(BufferView&& other) noexcept -> BufferView& { + if (&other != this) { + m_data = other.m_data; + m_size = other.m_size; + } + + return *this; + } + + // ==================================================================== + // Copyable + // ==================================================================== + BufferView(const BufferView& other) = default; + + auto operator=(const BufferView& other) noexcept -> BufferView& { + if (&other == this) { + return *this; + } + + m_data = other.m_data; + m_size = other.m_size; + return *this; + } + + ~BufferView() = default; + + // ==================================================================== + // Equivalence + // ==================================================================== + auto operator==(const BufferView& other) const noexcept -> bool { + return m_data == other.m_data && m_size == other.m_size; + } + + auto operator!=(const BufferView& other) const noexcept -> bool { + return !(*this == other); + } + + // ==================================================================== + // Iterable + // ==================================================================== + using Iterator = BytesIterator; + + [[nodiscard]] + auto begin() const noexcept -> BytesIterator { + return {m_data, m_size, 0}; + } + + [[nodiscard]] + auto end() const noexcept -> BytesIterator { + return {m_data, m_size, m_size}; + } +}; + +static_assert(Moveable); +static_assert(Copyable); +static_assert(PartialEquivalence); +static_assert(Iterable); + +} // namespace br diff --git a/src/foundation/byte.cppm b/src/foundation/byte.cppm new file mode 100644 index 0000000..c569f90 --- /dev/null +++ b/src/foundation/byte.cppm @@ -0,0 +1,62 @@ +export module bedrock.foundation:byte; + +import bedrock.numbers; +import bedrock.semantics; + +namespace br { + +export enum class byte : uint8 {}; + +export constexpr auto operator""_b(unsigned long long value) noexcept -> byte { + return static_cast(static_cast(value)); +} + +export template +constexpr auto operator<<(byte datum, IntegerType shift) noexcept -> byte { + return static_cast(static_cast(datum) << shift); +} + +export template +constexpr auto operator>>(byte datum, IntegerType shift) noexcept -> byte { + return static_cast(static_cast(datum) >> shift); +} + +export template +constexpr auto operator<<=(byte& datum, IntegerType shift) noexcept -> byte& { + return datum = datum << shift; +} + +export template +constexpr auto operator>>=(byte& datum, IntegerType shift) noexcept -> byte& { + return datum = datum >> shift; +} + +export constexpr auto operator|(byte left, byte right) noexcept -> byte { + return static_cast(static_cast(left) | static_cast(right)); +} + +export constexpr auto operator&(byte left, byte right) noexcept -> byte { + return static_cast(static_cast(left) & static_cast(right)); +} + +export constexpr auto operator^(byte left, byte right) noexcept -> byte { + return static_cast(static_cast(left) ^ static_cast(right)); +} + +export constexpr auto operator~(byte datum) noexcept -> byte { + return static_cast(~static_cast(datum)); +} + +export constexpr auto operator|=(byte& left, byte right) noexcept -> byte& { + return left = left | right; +} + +export constexpr auto operator&=(byte& left, byte right) noexcept -> byte& { + return left = left & right; +} + +export constexpr auto operator^=(byte& left, byte right) noexcept -> byte& { + return left = left ^ right; +} + +} // namespace br diff --git a/src/foundation/bytes_iterator.cppm b/src/foundation/bytes_iterator.cppm new file mode 100644 index 0000000..003e66b --- /dev/null +++ b/src/foundation/bytes_iterator.cppm @@ -0,0 +1,90 @@ +export module bedrock.foundation:bytes_iterator; + +import bedrock.numbers; +import bedrock.semantics; +import :byte; + +namespace br { + +export class BytesIterator final { + private: + byte* m_data; + usize m_current_index; + usize m_end_index; + + public: + BytesIterator(byte* data, usize n_bytes, usize current_index) + : m_data(data), m_current_index(current_index), m_end_index(n_bytes) {} + + // ==================================================================== + // Moveable + // ==================================================================== + BytesIterator(BytesIterator&& other) + : BytesIterator(other.m_data, other.m_end_index, other.m_current_index) {} + + auto operator=(BytesIterator&& other) noexcept -> BytesIterator& { + m_data = other.m_data; + m_end_index = other.m_end_index; + m_current_index = other.m_current_index; + return *this; + } + + ~BytesIterator() = default; + + // ==================================================================== + // !Copyable + // ==================================================================== + BytesIterator(const BytesIterator& other) = delete; + + auto operator=(const BytesIterator&) = delete; + + // ==================================================================== + // Iterator + // ==================================================================== + using Iterator = BytesIterator; + using Item = byte; + + [[nodiscard]] + auto begin() const noexcept -> BytesIterator { + return {m_data, m_end_index, 0}; + } + + [[nodiscard]] + auto end() const noexcept -> BytesIterator { + return {m_data, m_end_index, m_end_index}; + } + + [[nodiscard]] + auto operator*() const -> byte { + return m_data[m_current_index]; + } + + auto operator++() noexcept -> BytesIterator& { + if (m_current_index < m_end_index) { + ++m_current_index; + } + return *this; + } + + auto operator--() noexcept -> BytesIterator& { + if (m_current_index > 0) { + --m_current_index; + } + return *this; + } + + [[nodiscard]] + auto operator==(const BytesIterator& b) noexcept -> bool { + return m_data == b.m_data && m_end_index == b.m_end_index && m_current_index == b.m_current_index; + } + + auto operator!=(const BytesIterator& b) noexcept -> bool { + return !(*this == b); + } +}; + +static_assert(Moveable); +static_assert(!Copyable); +static_assert(BidirectionalIterator); + +} // namespace br diff --git a/src/foundation/iterators/adapter.cppm b/src/foundation/iterators/adapter.cppm new file mode 100644 index 0000000..407e3a9 --- /dev/null +++ b/src/foundation/iterators/adapter.cppm @@ -0,0 +1,20 @@ +export module bedrock.foundation:iterators.adapter; + +import bedrock.semantics; + +namespace br { + +export template +concept IteratorAdapter = Defaultable && requires(Adapter adapter, It &&iter) { adapter(forward(iter)); }; + +export template Adapter> +auto operator|(It &&iter, Adapter &&adapter) { + return adapter(forward(iter)); +}; + +export template ::Iterator> Adapter> +auto operator|(I &iter, Adapter &&adapter) { + return adapter(iter.begin()); +}; + +} // namespace br diff --git a/src/foundation/iterators/enumerate.cppm b/src/foundation/iterators/enumerate.cppm new file mode 100644 index 0000000..cb2a8f0 --- /dev/null +++ b/src/foundation/iterators/enumerate.cppm @@ -0,0 +1,105 @@ +export module bedrock.foundation:iterators.enumerate; + +import bedrock.numbers; +import bedrock.semantics; +import :iterators.adapter; +import :tuple; + +namespace br { + +export template +class EnumerateIterator final { + private: + It m_iter; + usize m_current_index = 0; + + public: + EnumerateIterator(It&& i) + : m_iter(move(i)) {} + + // ==================================================================== + // Moveable + // ==================================================================== + EnumerateIterator(EnumerateIterator&& other) + requires(Moveable) + : m_iter(move(other.m_iter)) {} + + auto operator=(EnumerateIterator&& other) noexcept -> EnumerateIterator& + requires(Moveable) + { + if (&other != this) { + m_iter = move(other.m_iter); + } + return *this; + } + + ~EnumerateIterator() = default; + + // ==================================================================== + // Copyable + // ==================================================================== + EnumerateIterator(const EnumerateIterator& other) + requires(Copyable) + : m_iter(other.m_iter) {} + + auto operator=(const EnumerateIterator& other) noexcept -> EnumerateIterator& + requires(Copyable) + { + if (&other != this) { + m_iter = other.m_iter; + } + return *this; + } + + // ==================================================================== + // Iterator + // ==================================================================== + using Item = Tuple; + + [[nodiscard]] + auto begin() const noexcept -> EnumerateIterator { + return EnumerateIterator(m_iter.begin()); + } + + [[nodiscard]] + auto end() const noexcept -> EnumerateIterator { + return EnumerateIterator(m_iter.end()); + } + + [[nodiscard]] + auto operator*() const -> Item { + return {*m_iter, m_current_index}; + } + + auto operator++() noexcept -> EnumerateIterator& { + ++m_iter; + ++m_current_index; + return *this; + } + + auto operator--() noexcept -> EnumerateIterator& + requires BidirectionalIterator + { + --m_iter; + --m_current_index; + return *this; + } + + [[nodiscard]] + auto operator==(const EnumerateIterator& other) noexcept -> bool { + return m_iter == other.m_iter; + } + + auto operator!=(const EnumerateIterator& other) noexcept -> bool { + return !(*this == other); + } +}; + +export struct enumerate final { + template + auto operator()(It&& it) noexcept -> EnumerateIterator { + return EnumerateIterator(move(it)); + } +}; + +} // namespace br diff --git a/src/foundation/iterators/reverse.cppm b/src/foundation/iterators/reverse.cppm new file mode 100644 index 0000000..c4ee938 --- /dev/null +++ b/src/foundation/iterators/reverse.cppm @@ -0,0 +1,101 @@ +export module bedrock.foundation:iterators.reverse; + +import bedrock.semantics; +import :iterators.adapter; + +namespace br { + +export template +class ReverseIterator { + private: + It m_iter; + + public: + ReverseIterator(It&& i) + : m_iter(move(i)) {} + + // ==================================================================== + // Moveable + // ==================================================================== + ReverseIterator(ReverseIterator&& other) + requires(Moveable) + : m_iter(move(other.m_iter)) {} + + auto operator=(ReverseIterator&& other) noexcept -> ReverseIterator& + requires(Moveable) + { + if (&other != this) { + m_iter = move(other.m_iter); + } + return *this; + } + + ~ReverseIterator() = default; + + // ==================================================================== + // Copyable + // ==================================================================== + ReverseIterator(const ReverseIterator& other) + requires(Copyable) + : m_iter(other.m_iter) {} + + auto operator=(const ReverseIterator& other) noexcept -> ReverseIterator& + requires(Copyable) + { + if (&other != this) { + m_iter = other.m_iter; + } + return *this; + } + + // ==================================================================== + // Iterator + // ==================================================================== + using Iterator = ReverseIterator; + + using Item = typename It::Item; + + [[nodiscard]] + auto begin() const noexcept -> ReverseIterator { + return ReverseIterator(m_iter.end()); + } + + [[nodiscard]] + auto end() const noexcept -> ReverseIterator { + return ReverseIterator(m_iter.begin()); + } + + [[nodiscard]] + auto operator*() const -> Item { + return *m_iter; + } + + auto operator++() noexcept -> ReverseIterator& { + --m_iter; + return *this; + } + + auto operator--() noexcept -> ReverseIterator& { + ++m_iter; + return *this; + } + + [[nodiscard]] + auto operator==(const ReverseIterator& other) noexcept -> bool { + return m_iter == other.m_iter; + } + + [[nodiscard]] + auto operator!=(const ReverseIterator& other) noexcept -> bool { + return !(*this == other); + } +}; + +export struct reverse final { + template + auto operator()(It&& it) noexcept -> ReverseIterator { + return ReverseIterator(move(it)); + } +}; + +} // namespace br diff --git a/src/foundation/optional.cppm b/src/foundation/optional.cppm new file mode 100644 index 0000000..a767628 --- /dev/null +++ b/src/foundation/optional.cppm @@ -0,0 +1,178 @@ +export module bedrock.foundation:optional; + +import bedrock.numbers; +import bedrock.semantics; + +namespace br { + +enum class OptionalVariant : uint8 { + Absent = 0, + Present = 1, +}; + +export template +union Optional final { + private: + struct Absent { + OptionalVariant tag; + } m_absent; + + struct Present { + T value; + OptionalVariant tag; + } m_present; + + public: + Optional() + : m_absent{.tag = OptionalVariant::Absent} {} + + explicit Optional(const T& value) + : m_present{.value = value, .tag = OptionalVariant::Present} {} + + [[nodiscard]] + static auto present(const T& value) noexcept -> Optional { + return Optional(value); + } + + [[nodiscard]] + static auto absent() noexcept -> Optional { + return Optional(); + } + + auto operator=(const T& value) -> Optional& + requires Copyable + { + this->m_present = {.value = value, .tag = OptionalVariant::Present}; + return *this; + } + + template M> + auto map(M mapper) -> Optional { + if (isAbsent()) { + return Optional::absent(); + } + return Optional::present(mapper(move(m_present.value))); + } + + template , T&&> M> + auto map(M mapper) -> Optional { + if (isAbsent()) { + return Optional::absent(); + } + return mapper(move(m_present.value)); + } + + [[nodiscard]] + auto valueOr(T& default_value) noexcept -> T& { + if (isAbsent()) { + return default_value; + } + return m_present.value; + } + + [[nodiscard]] + auto isAbsent() const noexcept -> bool { + return m_absent.tag == OptionalVariant::Absent; + } + + [[nodiscard]] + auto isPresent() const noexcept -> bool { + return !isAbsent(); + } + + [[nodiscard]] + auto operator==(const Optional& rhs) const noexcept -> bool { + if (isAbsent() && rhs.isAbsent()) { + return true; + } + if (isPresent() && rhs.isPresent()) { + return m_present.value == rhs.m_present.value; + } + return false; + } + + [[nodiscard]] + auto operator!=(const Optional& rhs) const noexcept -> bool { + return !(*this == rhs); + } + + [[nodiscard]] + auto operator==(const T& rhs) const noexcept -> bool { + if (isAbsent()) { + return false; + } + return m_present.value == rhs; + } + + [[nodiscard]] + auto operator!=(const T& rhs) const noexcept -> bool { + return !(*this == rhs); + } + + template P, Callable A> + void match(P&& present, A&& absent) const { + if (isPresent()) { + present(m_present.value); + } else { + absent(); + } + } + + // ==================================================================== + // Moveable + // ==================================================================== + [[nodiscard]] + Optional(Optional&& other) + requires Moveable + { + if (other.isAbsent()) { + m_absent = move(other.m_absent); + } else { + m_present = move(other.m_present); + } + } + + auto operator=(Optional&& other) -> Optional& + requires Moveable + { + if (&other != this) { + if (other.isAbsent()) { + m_absent = move(other.m_absent); + } else { + m_present = move(other.m_present); + } + } + return *this; + } + + ~Optional() {} + + // ==================================================================== + // Copyable + // ==================================================================== + [[nodiscard]] + Optional(const Optional& other) + requires Copyable + { + if (other.isAbsent()) { + m_absent = other.m_absent; + } else { + m_present = other.m_present; + } + } + + auto operator=(const Optional& other) -> Optional& + requires Copyable + { + if (&other != this) { + if (other.isAbsent()) { + m_absent = other.m_absent; + } else { + m_present = other.m_present; + } + } + return *this; + } +}; + +} // namespace br diff --git a/src/foundation/result.cppm b/src/foundation/result.cppm new file mode 100644 index 0000000..5598105 --- /dev/null +++ b/src/foundation/result.cppm @@ -0,0 +1,205 @@ +export module bedrock.foundation:result; + +import bedrock.semantics; +import :string_buffer; +import :string; + +namespace br { + +export class Error final { + private: + StringBuffer m_message; + + public: + explicit Error(const String& message) noexcept + : m_message(StringBuffer::copyFrom(message)) {} + + [[nodiscard]] + auto copy() const noexcept -> Error { + return Error(m_message); + } + + explicit operator String() const noexcept { + return m_message; + } + + // ==================================================================== + // !Moveable + // ==================================================================== + Error(Error&&) = default; + + auto operator=(Error&&) -> Error& = default; + + ~Error() = default; + + // ==================================================================== + // !Copyable + // ==================================================================== + Error(const Error&) = delete; + + auto operator=(const Error&) -> Error& = delete; + + // ==================================================================== + // Equivalence + // ==================================================================== + [[nodiscard]] + auto operator==(const Error& other) const noexcept -> bool { + return m_message == other.m_message; + } + + [[nodiscard]] + auto operator!=(const Error& other) const noexcept -> bool { + return !(*this == other); + } +}; + +export template <> +struct EquivalenceType { + static constexpr bool is_valid = true; +}; + +static_assert(Moveable); +static_assert(!Copyable); +static_assert(Equivalence); + +enum class ResultVariant : uint8 { + Success = 0, + Failure = 1, +}; + +template +struct Success { + T value; + ResultVariant tag; +}; + +template <> +struct Success { + ResultVariant tag; +}; + +struct Failure { + Error error; + ResultVariant tag; +}; + +static_assert(Moveable); + +export template +union [[nodiscard]] +Result final { + private: + Success m_success; + Failure m_failure; + + template + requires(!SameAs) + explicit Result(const U& value) + : m_success{.value = value, .tag = ResultVariant::Success} {} + + template + requires(SameAs) + explicit Result() + : m_success{.tag = ResultVariant::Success} {} + + explicit Result(Error&& error) + : m_failure{.error = move(error), .tag = ResultVariant::Failure} {} + + public: + // ==================================================================== + // Static Constructors + // ==================================================================== + template + requires(!SameAs) + [[nodiscard]] + static auto success(const U& value) noexcept -> Result { + return Result(value); + } + + template + requires(SameAs) + [[nodiscard]] + static auto success() noexcept -> Result { + return Result(); + } + + [[nodiscard]] + static auto failure(const String& message) noexcept -> Result { + return Result(Error(message)); + } + + // ==================================================================== + // Introspection Methods + // ==================================================================== + [[nodiscard]] + auto isSuccess() const noexcept -> bool { + return m_success.tag == ResultVariant::Success; + } + + [[nodiscard]] + auto isFailure() const noexcept -> bool { + return !isSuccess(); + } + + // ==================================================================== + // Moveable + // ==================================================================== + Result(Result&& other) noexcept + requires Moveable + { + if (&other != this) { + if (other.isFailure()) { + m_failure = move(other.m_failure); + } else { + m_success = move(other.m_success); + } + } + } + + auto operator=(Result&& other) noexcept -> Result& + requires Moveable + { + if (&other != this) { + if (other.isFailure()) { + m_failure = move(other.m_failure); + } else { + m_success = move(other.m_success); + } + } + return *this; + } + + ~Result() {} + + // ==================================================================== + // Copyable + // ==================================================================== + Result(const Result&) = delete; + + auto operator=(const Result& other) -> Result& = delete; + + // ==================================================================== + // Equivalence + // ==================================================================== + [[nodiscard]] + auto operator==(const Result& rhs) const noexcept -> bool + requires(Equivalence) + { + if (isFailure() && rhs.isFailure()) { + return m_failure.error == rhs.m_failure.error; + } + if (isSuccess() && rhs.isSuccess()) { + return m_success.value == rhs.m_success.value; + } + return false; + } + + [[nodiscard]] + auto operator!=(const Result& rhs) const noexcept -> bool + requires(Equivalence) + { + return !(*this == rhs); + } +}; + +} // namespace br diff --git a/src/foundation/string.cppm b/src/foundation/string.cppm new file mode 100644 index 0000000..41722ea --- /dev/null +++ b/src/foundation/string.cppm @@ -0,0 +1,130 @@ +export module bedrock.foundation:string; + +import bedrock.numbers; +import bedrock.semantics; +import :byte; +import :buffer; +import :bytes_iterator; +import :iterators.enumerate; +import :iterators.reverse; + +namespace br { + +export class String final { + private: + BufferView m_data; + + explicit String(BufferView&& view) + : m_data(view) {} + + public: + friend class StringBuffer; + + template + constexpr String(const char (&str)[Size]) + : m_data(reinterpret_cast(&str), Size - 1) {} + + [[nodiscard]] + auto byteSize() const noexcept -> usize { + return m_data.size(); + } + + [[nodiscard]] + auto characterLength() const noexcept -> usize { + usize nCharacters = 0; + for (auto byte : m_data) { + if ((byte & 0xc0_b) != 0x80_b) { + ++nCharacters; + } + } + return nCharacters; + } + + [[nodiscard]] + auto isEmpty() const noexcept -> bool { + return *this == ""; + } + + [[nodiscard]] + auto startsWith(const String& str) noexcept -> bool { + for (auto [byte, i] : str.bytes() | enumerate()) { + if (m_data[i] != byte) { + return false; + } + } + return true; + } + + [[nodiscard]] + auto endsWith(const String& str) noexcept -> bool { + for (auto [byte, i] : str.bytes() | reverse() | enumerate()) { + if (m_data[i] != byte) { + return false; + } + } + return true; + } + + // ==================================================================== + // Implicit conversions + // ==================================================================== + [[nodiscard]] operator BufferView() const noexcept { + return m_data; + } + + [[nodiscard]] + auto bytes() const noexcept -> BytesIterator { + return m_data.bytes(); + } + + // ==================================================================== + // Moveable + // ==================================================================== + String(String&& other) noexcept + : m_data(move(other.m_data)) {} + + auto operator=(String&& other) noexcept -> String& { + if (this == &other) { + m_data = move(other.m_data); + } + return *this; + } + + ~String() = default; + + // ==================================================================== + // Copyable + // ==================================================================== + String(const String& other) = default; + + auto operator=(const String& other) noexcept -> String& { + if (&other != this) { + m_data = other.m_data; + } + return *this; + } + + // ==================================================================== + // Equivalence + // ==================================================================== + [[nodiscard]] + auto operator==(const String& other) const noexcept -> bool { + return m_data == other.m_data; + } + + [[nodiscard]] + auto operator!=(const String& other) const noexcept -> bool { + return !(*this == other); + } +}; + +template <> +struct EquivalenceType { + static constexpr bool is_valid = true; +}; + +static_assert(Moveable); +static_assert(Copyable); +static_assert(Equivalence); + +} // namespace br diff --git a/src/foundation/string_buffer.cppm b/src/foundation/string_buffer.cppm new file mode 100644 index 0000000..145f6fc --- /dev/null +++ b/src/foundation/string_buffer.cppm @@ -0,0 +1,79 @@ +export module bedrock.foundation:string_buffer; + +import bedrock.numbers; +import bedrock.semantics; +import :string; +import :buffer; + +namespace br { + +export class StringBuffer final { + private: + Buffer m_data; + + explicit StringBuffer(Buffer&& buffer) + : m_data(move(buffer)) {} + + explicit StringBuffer(const String& str) + : StringBuffer(Buffer::copyFrom(str)) {} + + public: + [[nodiscard]] + static auto withCapacity(usize capacity) noexcept -> StringBuffer { + return StringBuffer(Buffer::withCapacity(capacity)); + } + + [[nodiscard]] + static auto copyFrom(const String& str) noexcept -> StringBuffer { + return StringBuffer(str); + } + + [[nodiscard]] + auto capacityBytes() const noexcept -> usize { + return m_data.capacity(); + } + + operator String() const noexcept { + return String(m_data); + } + + // ==================================================================== + // Moveable + // ==================================================================== + StringBuffer(StringBuffer&&) = default; + + auto operator=(StringBuffer&&) -> StringBuffer& = default; + + ~StringBuffer() = default; + + // ==================================================================== + // Copyable + // ==================================================================== + StringBuffer(const StringBuffer& other) = delete; + + auto operator=(const StringBuffer&) -> StringBuffer& = delete; + + // ==================================================================== + // Equivalence + // ==================================================================== + [[nodiscard]] + auto operator==(const StringBuffer& other) const noexcept -> bool { + return m_data == other.m_data; + } + + [[nodiscard]] + auto operator!=(const StringBuffer& other) const noexcept -> bool { + return !(*this == other); + } +}; + +export template <> +struct EquivalenceType { + static constexpr bool is_valid = true; +}; + +static_assert(Moveable); +static_assert(!Copyable); +static_assert(Equivalence); + +} // namespace br diff --git a/src/foundation/tuple.cppm b/src/foundation/tuple.cppm new file mode 100644 index 0000000..f6f18da --- /dev/null +++ b/src/foundation/tuple.cppm @@ -0,0 +1,33 @@ +export module bedrock.foundation:tuple; + +import bedrock.numbers; + +namespace br { + +export template +class Tuple; + +export template +class Tuple { + public: + Tuple(T1 t1, T2 t2) + : m_t1(t1), m_t2(t2) {} + + template + auto get() const -> auto { + if constexpr (I == 0) { + return m_t1; + } + if constexpr (I == 1) { + return m_t2; + } + } + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" + const T1 m_t1; + const T2 m_t2; +#pragma clang diagnostic pop +}; + +} // namespace br diff --git a/src/foundation/utility.cppm b/src/foundation/utility.cppm new file mode 100644 index 0000000..466b5e0 --- /dev/null +++ b/src/foundation/utility.cppm @@ -0,0 +1,19 @@ +export module bedrock.foundation:utility; + +import bedrock.semantics; + +namespace br { + +export template +[[nodiscard]] +constexpr auto nearestPowerOf2(T i) noexcept -> T { + --i; + i |= i >> 1; + i |= i >> 2; + i |= i >> 4; + i |= i >> 8; + i |= i >> 16; + return ++i; +}; + +} // namespace br diff --git a/src/io.cppm b/src/io.cppm new file mode 100644 index 0000000..b604d4e --- /dev/null +++ b/src/io.cppm @@ -0,0 +1,3 @@ +export module bedrock.io; + +export import :channels; diff --git a/src/io/channels.cppm b/src/io/channels.cppm new file mode 100644 index 0000000..af45158 --- /dev/null +++ b/src/io/channels.cppm @@ -0,0 +1,42 @@ +module; + +#include + +export module bedrock.io:channels; + +import bedrock.foundation; + +namespace br::io { + +class CommunicationChannel final { + private: + FILE* m_channel; + + public: + explicit constexpr CommunicationChannel(FILE* channel) + : m_channel(channel) {} + + auto write(const String& message) -> Result { + if (fflush(m_channel) == EOF) { + return Result::failure("failed to flush channel"); + } + auto bytes_written = + fwrite(static_cast(message).ptr(), sizeof(byte), message.byteSize(), m_channel); + if (bytes_written < message.byteSize()) { + return Result::failure("failed to write to channel"); + } + return Result::success(); + } +}; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmissing-variable-declarations" +#pragma clang diagnostic ignored "-Wuninitialized" +export CommunicationChannel out(stdout); + +export CommunicationChannel stderr(stderr); + +export CommunicationChannel stdin(stdin); +#pragma clang diagnostic pop + +} // namespace br::io diff --git a/src/numbers.cppm b/src/numbers.cppm new file mode 100644 index 0000000..5393995 --- /dev/null +++ b/src/numbers.cppm @@ -0,0 +1,31 @@ +export module bedrock.numbers; + +namespace br { + +export using usize = decltype(sizeof 0); +export using isize = decltype(static_cast(nullptr) - static_cast(nullptr)); + +export using uint8 = unsigned char; +static_assert(sizeof(uint8) == 1); +export using uint16 = unsigned short; +static_assert(sizeof(uint16) == 2); +export using uint32 = unsigned int; +static_assert(sizeof(uint32) == 4); +export using uint64 = unsigned long; +static_assert(sizeof(uint64) == 8); + +export using int8 = signed char; +static_assert(sizeof(int8) == 1); +export using int16 = signed short; +static_assert(sizeof(int16) == 2); +export using int32 = signed int; +static_assert(sizeof(int32) == 4); +export using int64 = signed long; +static_assert(sizeof(int64) == 8); + +export using float32 = float; +static_assert(sizeof(float32) == 4); +export using float64 = double; +static_assert(sizeof(float64) == 8); + +} // namespace br diff --git a/src/semantics.cppm b/src/semantics.cppm new file mode 100644 index 0000000..d30621a --- /dev/null +++ b/src/semantics.cppm @@ -0,0 +1,13 @@ +export module bedrock.semantics; + +export import :callable; +export import :copyable; +export import :defaultable; +export import :equivalence; +export import :floating_point; +export import :integral; +export import :iterable; +export import :moveable; +export import :ordered; +export import :referenceable; +export import :utility; diff --git a/src/semantics/callable.cppm b/src/semantics/callable.cppm new file mode 100644 index 0000000..1942113 --- /dev/null +++ b/src/semantics/callable.cppm @@ -0,0 +1,13 @@ +export module bedrock.semantics:callable; + +import :moveable; +import :utility; + +namespace br { + +export template +concept Callable = requires(T t, Args... args) { + { t(args...) } -> SameAs; +}; + +} // namespace br diff --git a/src/semantics/copyable.cppm b/src/semantics/copyable.cppm new file mode 100644 index 0000000..3d7fe99 --- /dev/null +++ b/src/semantics/copyable.cppm @@ -0,0 +1,13 @@ +export module bedrock.semantics:copyable; + +import :utility; + +namespace br { + +export template +concept Copyable = requires(T u, const T v) { + T(v); + { u = v } -> SameAs; +}; + +} // namespace br diff --git a/src/semantics/defaultable.cppm b/src/semantics/defaultable.cppm new file mode 100644 index 0000000..cb29f42 --- /dev/null +++ b/src/semantics/defaultable.cppm @@ -0,0 +1,12 @@ +export module bedrock.semantics:defaultable; + +import :utility; + +namespace br { + +export template +concept Defaultable = requires() { + { T() } -> SameAs; +}; + +} // namespace br diff --git a/src/semantics/equivalence.cppm b/src/semantics/equivalence.cppm new file mode 100644 index 0000000..9b08db9 --- /dev/null +++ b/src/semantics/equivalence.cppm @@ -0,0 +1,25 @@ +export module bedrock.semantics:equivalence; + +import :utility; +import :referenceable; +import :integral; + +namespace br { + +export template +concept PartialEquivalence = requires(Unref a, Unref b) { + { a == b } -> SameAs; + { a != b } -> SameAs; + { b == a } -> SameAs; + { b != a } -> SameAs; +}; + +export template +struct EquivalenceType { + static constexpr bool is_valid = false; +}; + +export template +concept Equivalence = PartialEquivalence && (EquivalenceType::is_valid || Integral); + +} // namespace br diff --git a/src/semantics/floating_point.cppm b/src/semantics/floating_point.cppm new file mode 100644 index 0000000..213d641 --- /dev/null +++ b/src/semantics/floating_point.cppm @@ -0,0 +1,28 @@ +export module bedrock.semantics:floating_point; + +namespace br { + +template +struct FloatingPointTraits { + static constexpr bool is_valid = false; +}; + +template <> +struct FloatingPointTraits { + static constexpr bool is_valid = true; +}; + +template <> +struct FloatingPointTraits { + static constexpr bool is_valid = true; +}; + +template <> +struct FloatingPointTraits { + static constexpr bool is_valid = true; +}; + +export template +concept FloatingPoint = FloatingPointTraits::is_valid; + +} // namespace br diff --git a/src/semantics/integral.cppm b/src/semantics/integral.cppm new file mode 100644 index 0000000..fbe837a --- /dev/null +++ b/src/semantics/integral.cppm @@ -0,0 +1,58 @@ +export module bedrock.semantics:integral; + +namespace br { + +export template +struct SignedIntegralTraits { + static constexpr bool is_valid = false; +}; + +template <> +struct SignedIntegralTraits { + static constexpr bool is_valid = true; +}; + +template <> +struct SignedIntegralTraits { + static constexpr bool is_valid = true; +}; + +template <> +struct SignedIntegralTraits { + static constexpr bool is_valid = true; +}; + +template <> +struct SignedIntegralTraits { + static constexpr bool is_valid = true; +}; + +template +struct UnsignedIntegralTraits { + static constexpr bool is_valid = false; +}; + +template <> +struct UnsignedIntegralTraits { + static constexpr bool is_valid = true; +}; + +template <> +struct UnsignedIntegralTraits { + static constexpr bool is_valid = true; +}; + +template <> +struct UnsignedIntegralTraits { + static constexpr bool is_valid = true; +}; + +template <> +struct UnsignedIntegralTraits { + static constexpr bool is_valid = true; +}; + +export template +concept Integral = SignedIntegralTraits::is_valid || UnsignedIntegralTraits::is_valid; + +} // namespace br diff --git a/src/semantics/iterable.cppm b/src/semantics/iterable.cppm new file mode 100644 index 0000000..751e6f7 --- /dev/null +++ b/src/semantics/iterable.cppm @@ -0,0 +1,44 @@ +export module bedrock.semantics:iterable; + +import bedrock.numbers; +import :ordered; +import :equivalence; +import :referenceable; +import :utility; + +namespace br { + +export template +concept Iterator = PartialEquivalence && requires(It r) { + typename UnreferencedType::Type::Item; + { ++r } -> SameAs; + { *r } -> SameAs::Type::Item>; +}; + +export template +concept Iterable = requires(I &r) { + requires(Iterator::Iterator>); + { r.begin() } -> SameAs::Iterator>; + { r.end() } -> SameAs::Iterator>; +}; + +export template +concept ForwardIterator = Iterable && Iterator; + +export template +concept BidirectionalIterator = ForwardIterator && requires(It r) { + { --r } -> SameAs; +}; + +export template +concept RandomAccessIterator = + BidirectionalIterator && TotallyOrdered && requires(It i, const It j, const isize n) { + { i += n } -> SameAs; + { j + n } -> SameAs; + { n + j } -> SameAs; + { i -= n } -> SameAs; + { j - n } -> SameAs; + { j[n] } -> SameAs; + }; + +} // namespace br diff --git a/src/semantics/moveable.cppm b/src/semantics/moveable.cppm new file mode 100644 index 0000000..2e632f8 --- /dev/null +++ b/src/semantics/moveable.cppm @@ -0,0 +1,25 @@ +export module bedrock.semantics:moveable; + +import :referenceable; +import :utility; + +namespace br { + +export template +concept Destructible = requires(T u) { + { u.~T() } noexcept; +}; + +export template +concept Moveable = Destructible && requires(Unref u, Unref rv) { + T(static_cast(rv)); + { u = static_cast(rv) } -> SameAs; +}; + +export template +[[nodiscard]] +auto move(T& value) noexcept -> Unref&& { + return static_cast&&>(value); +} + +} // namespace br diff --git a/src/semantics/ordered.cppm b/src/semantics/ordered.cppm new file mode 100644 index 0000000..125065f --- /dev/null +++ b/src/semantics/ordered.cppm @@ -0,0 +1,47 @@ +export module bedrock.semantics:ordered; + +import bedrock.numbers; +import :utility; +import :equivalence; +import :referenceable; + +namespace br { + +export enum class Ordering : int8 { + LessThan = -1, + EqualTo = 0, + GreaterThan = 1, +}; + +export template +concept PartiallyOrdered = requires(const Unref& a, const Unref& b) { + { a < b } -> SameAs; + { a > b } -> SameAs; + { a <= b } -> SameAs; + { a >= b } -> SameAs; + { b < a } -> SameAs; + { b > a } -> SameAs; + { b <= a } -> SameAs; + { b >= a } -> SameAs; +}; + +export template +concept TotallyOrdered = Equivalence && PartiallyOrdered; + +export template +[[nodiscard]] +auto max(const T& a, const T& b) -> bool + requires(TotallyOrdered) +{ + return a > b ? a : b; +}; + +export template +[[nodiscard]] +auto min(const T& a, const T& b) -> bool + requires(TotallyOrdered) +{ + return a < b ? a : b; +}; + +} // namespace br diff --git a/src/semantics/referenceable.cppm b/src/semantics/referenceable.cppm new file mode 100644 index 0000000..7b05dda --- /dev/null +++ b/src/semantics/referenceable.cppm @@ -0,0 +1,51 @@ +export module bedrock.semantics:referenceable; + +namespace br { + +export template +concept Referenceable = requires(T& u) { u; }; + +template +struct UnreferencedType { + using Type = T; +}; + +template +struct UnreferencedType { + using Type = T; +}; + +template +struct UnreferencedType { + using Type = const T; +}; + +template +struct UnreferencedType { + using Type = T; +}; + +template +struct UnreferencedType { + using Type = const T; +}; + +export template +using Unref = typename UnreferencedType::Type; + +export template +constexpr auto forward(Unref& t) noexcept -> Unref&& { + return static_cast(t); +}; + +export template +constexpr auto forward(Unref&& t) noexcept -> Unref&& { + return static_cast(t); +}; + +export template +auto declval() noexcept -> Unref& { + static_assert(false, "declval not allowed in an evaluated context"); +}; + +} // namespace br diff --git a/src/semantics/utility.cppm b/src/semantics/utility.cppm new file mode 100644 index 0000000..ea1ffbd --- /dev/null +++ b/src/semantics/utility.cppm @@ -0,0 +1,18 @@ +export module bedrock.semantics:utility; + +namespace br { + +template +struct areSameTypes { + static const bool same = false; +}; + +template +struct areSameTypes { + static const bool same = true; +}; + +export template +concept SameAs = areSameTypes::same; + +} // namespace br diff --git a/src/test.cppm b/src/test.cppm new file mode 100644 index 0000000..a13a7d2 --- /dev/null +++ b/src/test.cppm @@ -0,0 +1,3 @@ +export module bedrock.test; + +export import :api; diff --git a/src/test/api.cppm b/src/test/api.cppm new file mode 100644 index 0000000..0deac95 --- /dev/null +++ b/src/test/api.cppm @@ -0,0 +1,67 @@ +export module bedrock.test:api; + +import bedrock.semantics; +import bedrock.foundation; +import bedrock.collections; +#include + +namespace br { +struct Precondition { + String summary; + void (*definition)(); +}; + +struct Expectation { + String summary; + void (*definition)(); +}; + +struct Scenario { + String summary; + Sequence preconditions{}; + Sequence expectations{}; + + explicit Scenario(String summary) noexcept; +}; + +static auto SCENARIOS = Sequence::withCapacity(10000); + +export static auto allScenarios() -> const Sequence& { + return SCENARIOS; +} + +static Optional CURRENT_SCENARIO; + +export struct Feature final { + template + Feature(Scenarios... scenarios) noexcept {} +}; + +template +constexpr auto scenario(String summary, Def&& definition) noexcept -> void { + Scenario new_scenario(summary); + CURRENT_SCENARIO = &new_scenario; + + definition(); + + SCENARIOS.push(move(new_scenario)); + CURRENT_SCENARIO = Optional::absent(); +} + +template +constexpr auto given(String summary, Def&& definition = []() -> void {}) noexcept -> void { + CURRENT_SCENARIO.match( + [&](Scenario& scn) -> void { scn.preconditions.push({.summary = summary, .definition = definition}); }, + []() -> void { + // TODO: halt the program or something with an error message. + }); +} + +export template +constexpr auto then(String summary, Def&& definition) -> void { + CURRENT_SCENARIO.match( + [&](Scenario& scn) -> void { scn.expectations.push({.summary = summary, .definition = definition}); }, + []() -> void {}); +} + +} // namespace br diff --git a/src/test/runner.cppm b/src/test/runner.cppm new file mode 100644 index 0000000..1a2eb44 --- /dev/null +++ b/src/test/runner.cppm @@ -0,0 +1,17 @@ +export module bedrock.test:runner; + +import :api; + +using namespace br; + +int main() { + for (Scenario& scn : allScenarios()) { + for (Precondition& pre : scn.preconditions) { + pre.definition(); + } + + for (Expectation& exp : scn.expectations) { + exp.definition(); + } + } +}