Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 24 additions & 7 deletions cpp/include/coconext/types/logic.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <coconext/types/concepts.hpp>
#include <cstdint>
#include <format>
#include <optional>
#include <stdexcept>
#include <string_view>
#include <type_traits>
Expand All @@ -18,6 +19,8 @@ enum class ResolveMethod {
RANDOM,
};

class Bit;

class Logic {
public:
enum class value_type : uint8_t {
Expand All @@ -37,11 +40,15 @@ class Logic {
constexpr Logic(value_type value) noexcept : value_(value) {}
constexpr value_type value() const noexcept { return value_; }

constexpr bool is_resolvable() const noexcept {
return value_ == _0 || value_ == _1 || value_ == L || value_ == H;
}
// Resolve under `method`. Returns nullopt iff the value is not resolvable
// under `method` -- ERROR accepts only 0/1; WEAK additionally accepts L/H;
// ZEROS, ONES, RANDOM accept anything. This unifies the old separate
// is_resolvable/resolve pair: `r.has_value()` answers the predicate,
// `r.value()` extracts the Bit.
std::optional<Bit> resolve(ResolveMethod method) const noexcept;

Logic resolve(ResolveMethod method) const;
// Default to WEAK.
std::optional<Bit> resolve() const noexcept;

private:
value_type value_ = _0;
Expand All @@ -59,9 +66,11 @@ class Bit {
constexpr Bit(value_type value) noexcept : value_(value) {}
constexpr value_type value() const noexcept { return value_; }

constexpr bool is_resolvable() const noexcept { return true; }

constexpr Bit resolve(ResolveMethod) const noexcept { return *this; }
// Every Bit is resolvable under every method, so the optional is always
// engaged. Kept for uniformity with Logic::resolve so generic code over
// LogicType can treat both the same way.
constexpr std::optional<Bit> resolve(ResolveMethod) const noexcept { return *this; }
constexpr std::optional<Bit> resolve() const noexcept { return *this; }

// Implicit conversion from Bit to Logic mimics subtype upcasting.
constexpr operator Logic() const noexcept {
Expand All @@ -80,6 +89,12 @@ class Bit {
value_type value_ = _0;
};

// Out-of-line: needs the Bit definition above to instantiate
// std::optional<Bit>.
inline std::optional<Bit> Logic::resolve() const noexcept {
return resolve(ResolveMethod::WEAK);
}

constexpr bool operator==(Logic const& lhs, Logic const& rhs) noexcept {
return lhs.value() == rhs.value();
}
Expand Down Expand Up @@ -235,6 +250,8 @@ constexpr int to_int(Logic const& value) {
}
}

constexpr int to_int(Bit const& value) noexcept { return value.value() == Bit::_0 ? 0 : 1; }

constexpr Logic operator|(Logic const& lhs, Logic const& rhs) noexcept {
using enum Logic::value_type;
constexpr Logic const table[9][9] = {
Expand Down
117 changes: 52 additions & 65 deletions cpp/include/coconext/types/logic_array.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <cstddef>
#include <format>
#include <limits>
#include <optional>
#include <ranges>
#include <stdexcept>
#include <string>
Expand Down Expand Up @@ -78,46 +79,23 @@ constexpr Range make_logic_static_range() {
}
}

// CRTP mixin providing the Logic/Bit-specific query and resolution members.
// Inherited by the Logic/Bit specializations of Array, Vector, StaticArraySlice,
// and ArraySlice below. The non-Logic/Bit primaries do NOT inherit this,
// so `Array<int, R>::is_resolvable()` and friends don't exist.
} // namespace detail

template <>
class Vector<Bit>;

namespace detail {

// CRTP mixin providing the Logic/Bit-specific resolve member. Inherited by
// the Logic/Bit specializations of Array, Vector, StaticArraySlice, and
// ArraySlice below. The non-Logic/Bit primaries do NOT inherit this, so
// `Array<int, R>::resolve(method)` and friends don't exist.
template <typename Self>
struct LogicArrayMixin {
bool is_resolvable() const noexcept {
auto const& self = *static_cast<Self const*>(this);
using Elem = std::ranges::range_value_t<Self>;
if constexpr (std::same_as<Elem, Bit>) {
// Every Bit is resolvable; skip the walk.
return true;
} else {
return std::ranges::all_of(self, [](auto const& v) {
return v.is_resolvable();
});
}
}

// Element-wise resolve. Returns a static `Array<Elem, R>` when Self has a
// compile-time range, a heap-allocated `Vector<Elem>` otherwise. The
// returned array preserves Self's range (an owner returns the same shape;
// a slice returns an owner sized like the slice's range).
auto resolve(ResolveMethod method) const {
auto const& self = *static_cast<Self const*>(this);
using Elem = std::ranges::range_value_t<Self>;
if constexpr (StaticRangedSequence<Self>) {
::coconext::types::Array<Elem, Self::static_range> result{};
std::ranges::transform(self, result.begin(), [method](auto const& v) {
return v.resolve(method);
});
return result;
} else {
::coconext::types::Vector<Elem> result(self.range());
std::ranges::transform(self, result.begin(), [method](auto const& v) {
return v.resolve(method);
});
return result;
}
}
auto resolve(ResolveMethod method) const;
// Default to WEAK.
auto resolve() const { return resolve(ResolveMethod::WEAK); }

// Reductions: fold over the array with the corresponding bitwise op.
// Empty arrays return the operation's identity (1 for AND, 0 for OR/XOR),
Expand Down Expand Up @@ -160,9 +138,9 @@ struct LogicArrayMixin {
//
// These specializations make `Array<Logic, R>`, `Array<Bit, R>`,
// `Vector<Logic>`, `Vector<Bit>`, and slices over Logic/Bit owners
// inherit `LogicArrayMixin`, gaining `is_resolvable()` and `resolve(method)`
// as members. The primary templates remain unchanged for non-Logic element
// types -- e.g., `Array<int, R>` has no `is_resolvable()`.
// inherit `LogicArrayMixin`, gaining `resolve(method)` as a member. The
// primary templates remain unchanged for non-Logic element types -- e.g.,
// `Array<int, R>` has no `resolve(method)`.

namespace detail {

Expand Down Expand Up @@ -252,6 +230,40 @@ class StaticArraySlice<ArrayT, R>
using detail::StaticArraySliceImpl<ArrayT, R>::operator=;
};

namespace detail {

template <typename Self>
auto LogicArrayMixin<Self>::resolve(ResolveMethod method) const {
auto const& self = *static_cast<Self const*>(this);
if constexpr (StaticRangedSequence<Self>) {
std::optional<::coconext::types::detail::Array<Bit, Self::static_range>> result{
std::in_place
};
auto out = result->begin();
for (auto const& v : self) {
auto r = v.resolve(method);
if (!r) {
return decltype(result){std::nullopt};
}
*out++ = *r;
}
return result;
} else {
std::optional<::coconext::types::Vector<Bit>> result{std::in_place, self.range()};
auto out = result->begin();
for (auto const& v : self) {
auto r = v.resolve(method);
if (!r) {
return decltype(result){std::nullopt};
}
*out++ = *r;
}
return result;
}
}

} // namespace detail

using LogicVector = Vector<Logic>;
using BitVector = Vector<Bit>;

Expand Down Expand Up @@ -747,31 +759,6 @@ inline Vector<Bit> to_bit_array(std::string_view s) {
return detail::parse_logic_string<Bit>(s, [](char c) { return to_bit(c); });
}

// Convert a range of Logic to a Vector<Bit>. Throws if any element is not
// 0/1/L/H (i.e. every element must be resolvable). Constrained to sized_range
// so the result can be sized up-front from std::ranges::size in O(1) and the
// resolvability check can be fused with the fill into a single pass.
template <std::ranges::sized_range R>
requires std::same_as<std::ranges::range_value_t<R>, Logic>
Vector<Bit> to_bit_array(R const& range) {
auto const sz = std::ranges::size(range);
if (sz > static_cast<size_t>(std::numeric_limits<Range::value_type>::max())) {
throw std::length_error("range too long for Range::value_type");
}
auto const n = static_cast<Range::value_type>(sz);
Vector<Bit> result(Range{n - 1, Direction::DOWNTO, 0});
auto out = result.begin();
for (Logic const& v : range) {
if (!v.is_resolvable()) {
throw std::invalid_argument(
"Cannot convert non-resolvable Logic values to BitArray"
);
}
*out++ = to_int(v) ? Bit::_1 : Bit::_0;
}
return result;
}

// -- String-literal UDL ----------------------------------------------------

template <StringLiteral S>
Expand Down
53 changes: 23 additions & 30 deletions cpp/src/logic.cpp
Original file line number Diff line number Diff line change
@@ -1,79 +1,72 @@
#include <coconext/types/logic.hpp>
#include <optional>
#include <random>
#include <stdexcept>

#include "./random.hpp"

using namespace coconext::types;

namespace coconext::types {

Logic Logic::resolve(ResolveMethod method) const {
std::optional<Bit> Logic::resolve(ResolveMethod method) const noexcept {
switch (method) {
case ResolveMethod::ERROR:
switch (value_) {
case _0:
return Bit::_0;
case _1:
return *this;
return Bit::_1;
default:
throw std::invalid_argument("Logic value is not resolvable");
return std::nullopt;
}
case ResolveMethod::WEAK:
switch (value_) {
case _0:
case _1:
return *this;
case L:
return _0;
return Bit::_0;
case _1:
case H:
return _1;
case W:
return X;
return Bit::_1;
default:
return *this;
return std::nullopt;
}
case ResolveMethod::ZEROS:
switch (value_) {
case _0:
case _1:
return *this;
case L:
return _0;
return Bit::_0;
case _1:
case H:
return _1;
return Bit::_1;
default:
return _0;
return Bit::_0;
}
case ResolveMethod::ONES:
switch (value_) {
case _0:
case _1:
return *this;
case L:
return _0;
return Bit::_0;
case _1:
case H:
return _1;
return Bit::_1;
default:
return _1;
return Bit::_1;
}
case ResolveMethod::RANDOM: {
case ResolveMethod::RANDOM:
switch (value_) {
case _0:
case _1:
return *this;
case L:
return _0;
return Bit::_0;
case _1:
case H:
return _1;
return Bit::_1;
default: {
auto& rng = get_rng();
return (rng() % 2 == 0) ? _0 : _1;
return (rng() % 2 == 0) ? Bit::_0 : Bit::_1;
}
}
}
default:
throw std::invalid_argument("Unknown resolve method");
}
return std::nullopt;
}

} // namespace coconext::types
Loading
Loading