Initial commit

This commit is contained in:
M. George Hansen 2025-06-07 14:17:56 -06:00
commit 91d54c58d5
Signed by: mgeorgehansen
SSH key fingerprint: SHA256:JlIGiQLPyQ2RHTH3a2oVlb20Xkh9Glr8DUF4YTXHJxM
42 changed files with 2212 additions and 0 deletions

22
.clang-format Normal file
View file

@ -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
...

14
.gitignore vendored Normal file
View file

@ -0,0 +1,14 @@
*
!/.gitignore
!/README.md
!/CMakeLists.txt
!/.clang-format
!/src/
!/src/**/
!/src/**/*.cppm
!/test/
!/test/**/
!/test/**/*.cpp

94
CMakeLists.txt Normal file
View file

@ -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)

12
README.md Normal file
View file

@ -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.

7
src/bedrock.cppm Normal file
View file

@ -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;

3
src/cli.cppm Normal file
View file

@ -0,0 +1,3 @@
export module bedrock.cli;
export import :program;

46
src/cli/program.cppm Normal file
View file

@ -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<ProgramArg> args;
};
export template <typename... Args>
class ProgramArgs final {
private:
Tuple<Args...> m_args;
};
export class Program final {
private:
String m_program_name;
Sequence<ProgramArg> m_args;
public:
Program(const String program_name, ProgramOptions&& options)
: m_program_name(program_name), m_args(move(options.args)) {}
template <Callable<Result<void>, const Hashmap<String, String>&> C>
auto run(int, char**, C op) -> int {
auto args = Hashmap<String, String>::withCapacity(0);
auto result = op(args);
if (result.isFailure()) {
return 1;
}
return 0;
}
};
} // namespace br

4
src/collections.cppm Normal file
View file

@ -0,0 +1,4 @@
export module bedrock.collections;
export import :hashmap;
export import :sequence;

View file

@ -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<const BufferView&>(key).bytes()) {
hash ^= static_cast<usize>(byte);
hash *= fnv_prime;
}
return hash;
};
export template <typename Key, typename Value>
class Hashmap final {
private:
struct Slot {
Key key;
Optional<Value> value;
[[nodiscard]]
auto isUnoccupied() const noexcept -> bool {
return key.isEmpty();
}
};
Sequence<Slot> m_slots;
explicit Hashmap(usize capacity)
: m_slots(Sequence<Slot>::withCapacity(capacity * sizeof(Slot))) {}
[[nodiscard]]
auto loadFactor() -> float32 {
return static_cast<float32>(m_slots.usedCapacity()) / m_slots.capacity();
}
[[nodiscard]]
auto findSlot(const Key& key) const noexcept -> Optional<Slot&> {
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<const Value&> {
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<Key>::absent();
}
};
} // namespace br

View file

@ -0,0 +1,53 @@
export module bedrock.collections:sequence;
import bedrock.numbers;
import bedrock.foundation;
namespace br {
export template <typename T>
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 <typename... Args>
Sequence(Args&&... items)
: m_items(Buffer::withCapacity(sizeof...(items) * sizeof(T))) {
(push(forward<T>(items)), ...);
}
auto push(T&& item) noexcept -> bool {
return m_items.write(forward<T>(item), m_items.usedSize());
}
[[nodiscard]]
auto operator[](usize index) const noexcept -> Optional<const T&> {
return m_items.slice(sizeof(T), index * sizeof(T)).map([](BufferView view) -> const T& {
return view.reinterpret<T>();
});
}
auto insert(usize index, T&& value) const noexcept -> bool {
return m_items.write<T>(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

14
src/foundation.cppm Normal file
View file

@ -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;

193
src/foundation/buffer.cppm Normal file
View file

@ -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 <typename T>
[[nodiscard]]
auto write(T&& object, usize start_at = 0) noexcept -> bool {
if (start_at + sizeof(T) > m_capacity) {
return false;
}
auto* bytes = reinterpret_cast<byte*>(&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>(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<BufferView> {
return static_cast<BufferView>(*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<Buffer>);
static_assert(!Copyable<Buffer>);
static_assert(PartialEquivalence<Buffer>);
} // namespace br

View file

@ -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<byte*>(data)), m_size(size) {}
template <usize Size>
constexpr BufferView(byte const (&bytes)[Size])
: BufferView(static_cast<const byte (&)[Size]>(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<byte> {
if (index >= m_size) {
return Optional<byte>::absent();
}
return Optional<byte>::present(m_data[index]);
}
[[nodiscard]]
auto slice(usize size, usize offset = 0) const noexcept -> Optional<BufferView> {
if (offset + size >= m_size) {
return Optional<BufferView>::absent();
}
return Optional<BufferView>::present({m_data + offset, size});
}
template <typename T>
[[nodiscard]]
auto reinterpret() const noexcept -> Optional<const T&> {
if (sizeof(T) >= m_size) {
return Optional<const T&>::absent();
}
return Optional<const T&>::present(reinterpret_cast<const T&>(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<BufferView>);
static_assert(Copyable<BufferView>);
static_assert(PartialEquivalence<BufferView>);
static_assert(Iterable<BufferView>);
} // namespace br

62
src/foundation/byte.cppm Normal file
View file

@ -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<byte>(static_cast<uint8>(value));
}
export template <Integral IntegerType>
constexpr auto operator<<(byte datum, IntegerType shift) noexcept -> byte {
return static_cast<byte>(static_cast<uint8>(datum) << shift);
}
export template <Integral IntegerType>
constexpr auto operator>>(byte datum, IntegerType shift) noexcept -> byte {
return static_cast<byte>(static_cast<uint8>(datum) >> shift);
}
export template <Integral IntegerType>
constexpr auto operator<<=(byte& datum, IntegerType shift) noexcept -> byte& {
return datum = datum << shift;
}
export template <Integral IntegerType>
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<byte>(static_cast<uint8>(left) | static_cast<uint8>(right));
}
export constexpr auto operator&(byte left, byte right) noexcept -> byte {
return static_cast<byte>(static_cast<uint8>(left) & static_cast<uint8>(right));
}
export constexpr auto operator^(byte left, byte right) noexcept -> byte {
return static_cast<byte>(static_cast<uint8>(left) ^ static_cast<uint8>(right));
}
export constexpr auto operator~(byte datum) noexcept -> byte {
return static_cast<byte>(~static_cast<uint8>(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

View file

@ -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<BytesIterator>);
static_assert(!Copyable<BytesIterator>);
static_assert(BidirectionalIterator<BytesIterator>);
} // namespace br

View file

@ -0,0 +1,20 @@
export module bedrock.foundation:iterators.adapter;
import bedrock.semantics;
namespace br {
export template <typename Adapter, typename It>
concept IteratorAdapter = Defaultable<Adapter> && requires(Adapter adapter, It &&iter) { adapter(forward<It>(iter)); };
export template <Iterator It, IteratorAdapter<It> Adapter>
auto operator|(It &&iter, Adapter &&adapter) {
return adapter(forward<It>(iter));
};
export template <Iterable I, IteratorAdapter<typename Unref<I>::Iterator> Adapter>
auto operator|(I &iter, Adapter &&adapter) {
return adapter(iter.begin());
};
} // namespace br

View file

@ -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 <ForwardIterator It>
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<It>)
: m_iter(move(other.m_iter)) {}
auto operator=(EnumerateIterator&& other) noexcept -> EnumerateIterator&
requires(Moveable<It>)
{
if (&other != this) {
m_iter = move(other.m_iter);
}
return *this;
}
~EnumerateIterator() = default;
// ====================================================================
// Copyable
// ====================================================================
EnumerateIterator(const EnumerateIterator& other)
requires(Copyable<It>)
: m_iter(other.m_iter) {}
auto operator=(const EnumerateIterator& other) noexcept -> EnumerateIterator&
requires(Copyable<It>)
{
if (&other != this) {
m_iter = other.m_iter;
}
return *this;
}
// ====================================================================
// Iterator
// ====================================================================
using Item = Tuple<typename It::Item, usize>;
[[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<It>
{
--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 <ForwardIterator It>
auto operator()(It&& it) noexcept -> EnumerateIterator<It> {
return EnumerateIterator<It>(move(it));
}
};
} // namespace br

View file

@ -0,0 +1,101 @@
export module bedrock.foundation:iterators.reverse;
import bedrock.semantics;
import :iterators.adapter;
namespace br {
export template <BidirectionalIterator It>
class ReverseIterator {
private:
It m_iter;
public:
ReverseIterator(It&& i)
: m_iter(move(i)) {}
// ====================================================================
// Moveable
// ====================================================================
ReverseIterator(ReverseIterator&& other)
requires(Moveable<It>)
: m_iter(move(other.m_iter)) {}
auto operator=(ReverseIterator&& other) noexcept -> ReverseIterator&
requires(Moveable<It>)
{
if (&other != this) {
m_iter = move(other.m_iter);
}
return *this;
}
~ReverseIterator() = default;
// ====================================================================
// Copyable
// ====================================================================
ReverseIterator(const ReverseIterator& other)
requires(Copyable<It>)
: m_iter(other.m_iter) {}
auto operator=(const ReverseIterator& other) noexcept -> ReverseIterator&
requires(Copyable<It>)
{
if (&other != this) {
m_iter = other.m_iter;
}
return *this;
}
// ====================================================================
// Iterator
// ====================================================================
using Iterator = ReverseIterator<It>;
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 <BidirectionalIterator It>
auto operator()(It&& it) noexcept -> ReverseIterator<It> {
return ReverseIterator<It>(move(it));
}
};
} // namespace br

View file

@ -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 <typename T>
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<T>
{
this->m_present = {.value = value, .tag = OptionalVariant::Present};
return *this;
}
template <typename U, Callable<U, T&&> M>
auto map(M mapper) -> Optional<U> {
if (isAbsent()) {
return Optional<U>::absent();
}
return Optional<U>::present(mapper(move(m_present.value)));
}
template <typename U, Callable<Optional<U>, T&&> M>
auto map(M mapper) -> Optional<U> {
if (isAbsent()) {
return Optional<U>::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<T>& 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<T>& 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 <Callable<void, T> P, Callable<void> A>
void match(P&& present, A&& absent) const {
if (isPresent()) {
present(m_present.value);
} else {
absent();
}
}
// ====================================================================
// Moveable
// ====================================================================
[[nodiscard]]
Optional(Optional&& other)
requires Moveable<T>
{
if (other.isAbsent()) {
m_absent = move(other.m_absent);
} else {
m_present = move(other.m_present);
}
}
auto operator=(Optional&& other) -> Optional&
requires Moveable<T>
{
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<T>
{
if (other.isAbsent()) {
m_absent = other.m_absent;
} else {
m_present = other.m_present;
}
}
auto operator=(const Optional& other) -> Optional&
requires Copyable<T>
{
if (&other != this) {
if (other.isAbsent()) {
m_absent = other.m_absent;
} else {
m_present = other.m_present;
}
}
return *this;
}
};
} // namespace br

205
src/foundation/result.cppm Normal file
View file

@ -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<Error> {
static constexpr bool is_valid = true;
};
static_assert(Moveable<Error>);
static_assert(!Copyable<Error>);
static_assert(Equivalence<Error>);
enum class ResultVariant : uint8 {
Success = 0,
Failure = 1,
};
template <typename T>
struct Success {
T value;
ResultVariant tag;
};
template <>
struct Success<void> {
ResultVariant tag;
};
struct Failure {
Error error;
ResultVariant tag;
};
static_assert(Moveable<Failure>);
export template <typename T>
union [[nodiscard]]
Result final {
private:
Success<T> m_success;
Failure m_failure;
template <typename U = T>
requires(!SameAs<U, void>)
explicit Result(const U& value)
: m_success{.value = value, .tag = ResultVariant::Success} {}
template <typename U = T>
requires(SameAs<U, void>)
explicit Result()
: m_success{.tag = ResultVariant::Success} {}
explicit Result(Error&& error)
: m_failure{.error = move(error), .tag = ResultVariant::Failure} {}
public:
// ====================================================================
// Static Constructors
// ====================================================================
template <typename U = T>
requires(!SameAs<U, void>)
[[nodiscard]]
static auto success(const U& value) noexcept -> Result {
return Result(value);
}
template <typename U = T>
requires(SameAs<U, void>)
[[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<T>
{
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<T>
{
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<T>)
{
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<T>)
{
return !(*this == rhs);
}
};
} // namespace br

130
src/foundation/string.cppm Normal file
View file

@ -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 <usize Size>
constexpr String(const char (&str)[Size])
: m_data(reinterpret_cast<const byte*>(&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<String> {
static constexpr bool is_valid = true;
};
static_assert(Moveable<String>);
static_assert(Copyable<String>);
static_assert(Equivalence<String>);
} // namespace br

View file

@ -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<StringBuffer> {
static constexpr bool is_valid = true;
};
static_assert(Moveable<StringBuffer>);
static_assert(!Copyable<StringBuffer>);
static_assert(Equivalence<StringBuffer>);
} // namespace br

33
src/foundation/tuple.cppm Normal file
View file

@ -0,0 +1,33 @@
export module bedrock.foundation:tuple;
import bedrock.numbers;
namespace br {
export template <typename... Types>
class Tuple;
export template <typename T1, typename T2>
class Tuple<T1, T2> {
public:
Tuple(T1 t1, T2 t2)
: m_t1(t1), m_t2(t2) {}
template <usize I>
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

View file

@ -0,0 +1,19 @@
export module bedrock.foundation:utility;
import bedrock.semantics;
namespace br {
export template <Integral T>
[[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

3
src/io.cppm Normal file
View file

@ -0,0 +1,3 @@
export module bedrock.io;
export import :channels;

42
src/io/channels.cppm Normal file
View file

@ -0,0 +1,42 @@
module;
#include <cstdio>
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<void> {
if (fflush(m_channel) == EOF) {
return Result<void>::failure("failed to flush channel");
}
auto bytes_written =
fwrite(static_cast<BufferView>(message).ptr(), sizeof(byte), message.byteSize(), m_channel);
if (bytes_written < message.byteSize()) {
return Result<void>::failure("failed to write to channel");
}
return Result<void>::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

31
src/numbers.cppm Normal file
View file

@ -0,0 +1,31 @@
export module bedrock.numbers;
namespace br {
export using usize = decltype(sizeof 0);
export using isize = decltype(static_cast<usize*>(nullptr) - static_cast<usize*>(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

13
src/semantics.cppm Normal file
View file

@ -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;

View file

@ -0,0 +1,13 @@
export module bedrock.semantics:callable;
import :moveable;
import :utility;
namespace br {
export template <typename T, typename R = void, typename... Args>
concept Callable = requires(T t, Args... args) {
{ t(args...) } -> SameAs<R>;
};
} // namespace br

View file

@ -0,0 +1,13 @@
export module bedrock.semantics:copyable;
import :utility;
namespace br {
export template <typename T>
concept Copyable = requires(T u, const T v) {
T(v);
{ u = v } -> SameAs<T &>;
};
} // namespace br

View file

@ -0,0 +1,12 @@
export module bedrock.semantics:defaultable;
import :utility;
namespace br {
export template <typename T>
concept Defaultable = requires() {
{ T() } -> SameAs<T>;
};
} // namespace br

View file

@ -0,0 +1,25 @@
export module bedrock.semantics:equivalence;
import :utility;
import :referenceable;
import :integral;
namespace br {
export template <typename T, typename U = T>
concept PartialEquivalence = requires(Unref<T> a, Unref<U> b) {
{ a == b } -> SameAs<bool>;
{ a != b } -> SameAs<bool>;
{ b == a } -> SameAs<bool>;
{ b != a } -> SameAs<bool>;
};
export template <PartialEquivalence T>
struct EquivalenceType {
static constexpr bool is_valid = false;
};
export template <typename T>
concept Equivalence = PartialEquivalence<T> && (EquivalenceType<T>::is_valid || Integral<T>);
} // namespace br

View file

@ -0,0 +1,28 @@
export module bedrock.semantics:floating_point;
namespace br {
template <typename T>
struct FloatingPointTraits {
static constexpr bool is_valid = false;
};
template <>
struct FloatingPointTraits<float> {
static constexpr bool is_valid = true;
};
template <>
struct FloatingPointTraits<double> {
static constexpr bool is_valid = true;
};
template <>
struct FloatingPointTraits<long double> {
static constexpr bool is_valid = true;
};
export template <typename T>
concept FloatingPoint = FloatingPointTraits<T>::is_valid;
} // namespace br

View file

@ -0,0 +1,58 @@
export module bedrock.semantics:integral;
namespace br {
export template <typename T>
struct SignedIntegralTraits {
static constexpr bool is_valid = false;
};
template <>
struct SignedIntegralTraits<signed char> {
static constexpr bool is_valid = true;
};
template <>
struct SignedIntegralTraits<signed short> {
static constexpr bool is_valid = true;
};
template <>
struct SignedIntegralTraits<signed int> {
static constexpr bool is_valid = true;
};
template <>
struct SignedIntegralTraits<signed long> {
static constexpr bool is_valid = true;
};
template <typename T>
struct UnsignedIntegralTraits {
static constexpr bool is_valid = false;
};
template <>
struct UnsignedIntegralTraits<unsigned char> {
static constexpr bool is_valid = true;
};
template <>
struct UnsignedIntegralTraits<unsigned short> {
static constexpr bool is_valid = true;
};
template <>
struct UnsignedIntegralTraits<unsigned int> {
static constexpr bool is_valid = true;
};
template <>
struct UnsignedIntegralTraits<unsigned long> {
static constexpr bool is_valid = true;
};
export template <typename T>
concept Integral = SignedIntegralTraits<T>::is_valid || UnsignedIntegralTraits<T>::is_valid;
} // namespace br

View file

@ -0,0 +1,44 @@
export module bedrock.semantics:iterable;
import bedrock.numbers;
import :ordered;
import :equivalence;
import :referenceable;
import :utility;
namespace br {
export template <typename It>
concept Iterator = PartialEquivalence<It> && requires(It r) {
typename UnreferencedType<It>::Type::Item;
{ ++r } -> SameAs<It &>;
{ *r } -> SameAs<typename UnreferencedType<It>::Type::Item>;
};
export template <typename I>
concept Iterable = requires(I &r) {
requires(Iterator<typename Unref<I>::Iterator>);
{ r.begin() } -> SameAs<typename Unref<I>::Iterator>;
{ r.end() } -> SameAs<typename Unref<I>::Iterator>;
};
export template <typename It>
concept ForwardIterator = Iterable<It> && Iterator<It>;
export template <typename It>
concept BidirectionalIterator = ForwardIterator<It> && requires(It r) {
{ --r } -> SameAs<It &>;
};
export template <typename It>
concept RandomAccessIterator =
BidirectionalIterator<It> && TotallyOrdered<It> && requires(It i, const It j, const isize n) {
{ i += n } -> SameAs<It &>;
{ j + n } -> SameAs<It>;
{ n + j } -> SameAs<It>;
{ i -= n } -> SameAs<It &>;
{ j - n } -> SameAs<It>;
{ j[n] } -> SameAs<isize>;
};
} // namespace br

View file

@ -0,0 +1,25 @@
export module bedrock.semantics:moveable;
import :referenceable;
import :utility;
namespace br {
export template <typename T>
concept Destructible = requires(T u) {
{ u.~T() } noexcept;
};
export template <typename T>
concept Moveable = Destructible<T> && requires(Unref<T> u, Unref<T> rv) {
T(static_cast<T &&>(rv));
{ u = static_cast<T &&>(rv) } -> SameAs<T&>;
};
export template <Moveable T>
[[nodiscard]]
auto move(T& value) noexcept -> Unref<T>&& {
return static_cast<Unref<T>&&>(value);
}
} // namespace br

View file

@ -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 <typename T, typename U = T>
concept PartiallyOrdered = requires(const Unref<T>& a, const Unref<U>& b) {
{ a < b } -> SameAs<bool>;
{ a > b } -> SameAs<bool>;
{ a <= b } -> SameAs<bool>;
{ a >= b } -> SameAs<bool>;
{ b < a } -> SameAs<bool>;
{ b > a } -> SameAs<bool>;
{ b <= a } -> SameAs<bool>;
{ b >= a } -> SameAs<bool>;
};
export template <typename T>
concept TotallyOrdered = Equivalence<T> && PartiallyOrdered<T>;
export template <typename T>
[[nodiscard]]
auto max(const T& a, const T& b) -> bool
requires(TotallyOrdered<T>)
{
return a > b ? a : b;
};
export template <typename T>
[[nodiscard]]
auto min(const T& a, const T& b) -> bool
requires(TotallyOrdered<T>)
{
return a < b ? a : b;
};
} // namespace br

View file

@ -0,0 +1,51 @@
export module bedrock.semantics:referenceable;
namespace br {
export template <typename T>
concept Referenceable = requires(T& u) { u; };
template <Referenceable T>
struct UnreferencedType {
using Type = T;
};
template <Referenceable T>
struct UnreferencedType<T&> {
using Type = T;
};
template <Referenceable T>
struct UnreferencedType<const T&> {
using Type = const T;
};
template <Referenceable T>
struct UnreferencedType<T&&> {
using Type = T;
};
template <Referenceable T>
struct UnreferencedType<const T&&> {
using Type = const T;
};
export template <Referenceable T>
using Unref = typename UnreferencedType<T>::Type;
export template <Referenceable T>
constexpr auto forward(Unref<T>& t) noexcept -> Unref<T>&& {
return static_cast<T&&>(t);
};
export template <Referenceable T>
constexpr auto forward(Unref<T>&& t) noexcept -> Unref<T>&& {
return static_cast<T&&>(t);
};
export template <Referenceable T>
auto declval() noexcept -> Unref<T>& {
static_assert(false, "declval not allowed in an evaluated context");
};
} // namespace br

View file

@ -0,0 +1,18 @@
export module bedrock.semantics:utility;
namespace br {
template <typename T, typename U>
struct areSameTypes {
static const bool same = false;
};
template <typename T>
struct areSameTypes<T, T> {
static const bool same = true;
};
export template <typename T, typename U>
concept SameAs = areSameTypes<T, U>::same;
} // namespace br

3
src/test.cppm Normal file
View file

@ -0,0 +1,3 @@
export module bedrock.test;
export import :api;

67
src/test/api.cppm Normal file
View file

@ -0,0 +1,67 @@
export module bedrock.test:api;
import bedrock.semantics;
import bedrock.foundation;
import bedrock.collections;
#include <utility>
namespace br {
struct Precondition {
String summary;
void (*definition)();
};
struct Expectation {
String summary;
void (*definition)();
};
struct Scenario {
String summary;
Sequence<Precondition> preconditions{};
Sequence<Expectation> expectations{};
explicit Scenario(String summary) noexcept;
};
static auto SCENARIOS = Sequence<Scenario>::withCapacity(10000);
export static auto allScenarios() -> const Sequence<Scenario*>& {
return SCENARIOS;
}
static Optional<Scenario*> CURRENT_SCENARIO;
export struct Feature final {
template <typename... Scenarios>
Feature(Scenarios... scenarios) noexcept {}
};
template <Callable Def>
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<Scenario*>::absent();
}
template <Callable Def>
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 <Callable Def>
constexpr auto then(String summary, Def&& definition) -> void {
CURRENT_SCENARIO.match(
[&](Scenario& scn) -> void { scn.expectations.push({.summary = summary, .definition = definition}); },
[]() -> void {});
}
} // namespace br

17
src/test/runner.cppm Normal file
View file

@ -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();
}
}
}