aboutsummaryrefslogtreecommitdiffstats
path: root/nihil.ucl/array.ccm
diff options
context:
space:
mode:
Diffstat (limited to 'nihil.ucl/array.ccm')
-rw-r--r--nihil.ucl/array.ccm468
1 files changed, 468 insertions, 0 deletions
diff --git a/nihil.ucl/array.ccm b/nihil.ucl/array.ccm
new file mode 100644
index 0000000..e3730ab
--- /dev/null
+++ b/nihil.ucl/array.ccm
@@ -0,0 +1,468 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <cassert>
+#include <cerrno>
+#include <cstdint>
+#include <cstdlib>
+#include <format>
+#include <iostream>
+#include <string>
+#include <system_error>
+#include <utility>
+
+#include <ucl.h>
+
+export module nihil.ucl:array;
+
+import :object;
+
+namespace nihil::ucl {
+
+export template<datatype T>
+struct array;
+
+export template<datatype T>
+struct array_iterator {
+ using difference_type = std::ptrdiff_t;
+ using value_type = T;
+ using reference = T&;
+ using pointer = T*;
+
+ array_iterator() = default;
+
+ [[nodiscard]] auto operator* (this array_iterator const &self) -> T
+ {
+ auto arr = self.get_array();
+ if (self.m_idx >= ::ucl_array_size(arr))
+ throw std::logic_error(
+ "nihil::ucl::array_iterator: "
+ "access past end of array");
+
+ auto uobj = ::ucl_array_find_index(arr, self.m_idx);
+ if (uobj == nullptr)
+ throw std::runtime_error(
+ "nihil::ucl::array_iterator: "
+ "failed to fetch UCL array index");
+
+ return T(nihil::ucl::ref, uobj);
+ }
+
+ [[nodiscard]] auto operator[] (this array_iterator const &self,
+ difference_type idx)
+ -> T
+ {
+ return *(self + idx);
+ }
+
+ auto operator++ (this array_iterator &self) -> array_iterator &
+ {
+ auto arr = self.get_array();
+ if (self.m_idx == ::ucl_array_size(arr))
+ throw std::logic_error(
+ "nihil::ucl::array_iterator: "
+ "iterating past end of array");
+
+ ++self.m_idx;
+ return self;
+ }
+
+ auto operator++ (this array_iterator &self, int) -> array_iterator
+ {
+ auto copy = self;
+ ++self;
+ return copy;
+ }
+
+ auto operator-- (this array_iterator &self) -> array_iterator&
+ {
+ if (self.m_idx == 0)
+ throw std::logic_error(
+ "nihil::ucl::array_iterator: "
+ "iterating before start of array");
+ --self.m_idx;
+ return self;
+ }
+
+ auto operator-- (this array_iterator &self, int) -> array_iterator
+ {
+ auto copy = self;
+ --self;
+ return copy;
+ }
+
+ [[nodiscard]] auto operator== (this array_iterator const &lhs,
+ array_iterator const &rhs)
+ -> bool
+ {
+ // Empty iterators should compare equal.
+ if (lhs.m_array == nullptr && rhs.m_array == nullptr)
+ return true;
+
+ if (lhs.get_array() != rhs.get_array())
+ throw std::logic_error(
+ "nihil::ucl::array_iterator: "
+ "comparing iterators of different arrays");
+
+ return lhs.m_idx == rhs.m_idx;
+ }
+
+ [[nodiscard]] auto operator<=> (this array_iterator const &lhs,
+ array_iterator const &rhs)
+ {
+ // Empty iterators should compare equal.
+ if (lhs.m_array == nullptr && rhs.m_array == nullptr)
+ return std::strong_ordering::equal;
+
+ if (lhs.get_array() != rhs.get_array())
+ throw std::logic_error(
+ "nihil::ucl::array_iterator: "
+ "comparing iterators of different arrays");
+
+ return lhs.m_idx <=> rhs.m_idx;
+ }
+
+ auto operator+= (this array_iterator &lhs, difference_type rhs)
+ -> array_iterator &
+ {
+ auto arr = lhs.get_array();
+ // m_idx cannot be greater than the array size
+ auto max_inc = ::ucl_array_size(arr) - lhs.m_idx;
+
+ if (std::cmp_greater(rhs, max_inc))
+ throw std::logic_error(
+ "nihil::ucl::array_iterator: "
+ "iterating past end of array");
+
+ lhs.m_idx += rhs;
+ return lhs;
+ }
+
+ auto operator-= (this array_iterator &lhs, difference_type rhs)
+ -> array_iterator &
+ {
+ if (std::cmp_greater(rhs, lhs.m_idx))
+ throw std::logic_error(
+ "nihil::ucl::array_iterator: "
+ "iterating before start of array");
+ lhs.m_idx -= rhs;
+ return lhs;
+ }
+
+ [[nodiscard]] auto operator- (this array_iterator const &lhs,
+ array_iterator const &rhs)
+ -> difference_type
+ {
+ if (lhs.get_array() != rhs.get_array())
+ throw std::logic_error(
+ "nihil::ucl::array_iterator: "
+ "comparing iterators of different arrays");
+
+ return lhs.m_idx - rhs.m_idx;
+ }
+
+private:
+ friend struct array<T>;
+
+ ::ucl_object_t const * m_array{};
+ std::size_t m_idx{};
+
+ [[nodiscard]] auto get_array(this array_iterator const &self)
+ -> ::ucl_object_t const *
+ {
+ if (self.m_array == nullptr)
+ throw std::logic_error(
+ "nihil::ucl::array_iterator: "
+ "attempt to access an empty iterator");
+
+ return self.m_array;
+ }
+
+ array_iterator(::ucl_object_t const *array, std::size_t idx)
+ : m_array(array)
+ , m_idx(idx)
+ {}
+};
+
+export template<datatype T> [[nodiscard]]
+auto operator+(array_iterator<T> const &lhs,
+ typename array_iterator<T>::difference_type rhs)
+-> array_iterator<T>
+{
+ auto copy = lhs;
+ copy += rhs;
+ return copy;
+}
+
+export template<datatype T> [[nodiscard]]
+auto operator+(typename array_iterator<T>::difference_type lhs,
+ array_iterator<T> const &rhs)
+ -> array_iterator<T>
+{
+ return rhs - lhs;
+}
+
+export template<datatype T> [[nodiscard]]
+auto operator-(array_iterator<T> const &lhs,
+ typename array_iterator<T>::difference_type rhs)
+ -> array_iterator<T>
+{
+ auto copy = lhs;
+ copy -= rhs;
+ return copy;
+}
+
+export template<datatype T = object>
+struct array final : object {
+ inline static constexpr object_type ucl_type = object_type::array;
+
+ using value_type = T;
+ using size_type = std::size_t;
+ using difference_type = std::ptrdiff_t;
+ using iterator = array_iterator<T>;
+
+ /*
+ * Create an empty array. Throws std::system_error on failure.
+ */
+ array() : object(noref, [] {
+ auto *uobj = ::ucl_object_typed_new(UCL_ARRAY);
+ if (uobj == nullptr)
+ throw std::system_error(
+ std::make_error_code(std::errc(errno)));
+ return uobj;
+ }())
+ {
+ }
+
+ /*
+ * Create an array from a UCL object. Throws type_mismatch
+ * on failure.
+ *
+ * Unlike object_cast<>, this does not check the type of the contained
+ * elements, which means object access can throw type_mismatch.
+ */
+ array(ref_t, ::ucl_object_t const *uobj)
+ : object(nihil::ucl::ref, [&] {
+ auto actual_type = static_cast<object_type>(
+ ::ucl_object_type(uobj));
+ if (actual_type != array::ucl_type)
+ throw type_mismatch(array::ucl_type,
+ actual_type);
+ return uobj;
+ }())
+ {
+ }
+
+ array(noref_t, ::ucl_object_t *uobj)
+ : object(nihil::ucl::noref, [&] {
+ auto actual_type = static_cast<object_type>(
+ ::ucl_object_type(uobj));
+ if (actual_type != array::ucl_type)
+ throw type_mismatch(array::ucl_type,
+ actual_type);
+ return uobj;
+ }())
+ {
+ }
+
+ /*
+ * Create an array from an iterator pair.
+ */
+ template<std::input_iterator Iterator>
+ requires(std::convertible_to<std::iter_value_t<Iterator>, T>)
+ array(Iterator first, Iterator last)
+ : array()
+ {
+ // This is exception safe, because if we throw here the
+ // base class destructor will free the array.
+ while (first != last) {
+ push_back(*first);
+ ++first;
+ }
+ }
+
+ /*
+ * Create an array from a range.
+ */
+ template<std::ranges::range Range>
+ requires(std::convertible_to<std::ranges::range_value_t<Range>, T>)
+ array(std::from_range_t, Range &&range)
+ : array(std::ranges::begin(range),
+ std::ranges::end(range))
+ {
+ }
+
+ /*
+ * Create an array from an initializer_list.
+ */
+ array(std::initializer_list<T> const &list)
+ : array(std::ranges::begin(list),
+ std::ranges::end(list))
+ {
+ }
+
+ /*
+ * Array iterator access.
+ */
+
+ [[nodiscard]] auto begin(this array const &self) -> iterator
+ {
+ return {self.get_ucl_object(), 0};
+ }
+
+ [[nodiscard]] auto end(this array const &self) -> iterator
+ {
+ return {self.get_ucl_object(), self.size()};
+ }
+
+ /*
+ * Return the size of this array.
+ */
+ [[nodiscard]] auto size(this array const &self) -> size_type
+ {
+ return ::ucl_array_size(self.get_ucl_object());
+ }
+
+ /*
+ * Test if this array is empty.
+ */
+ [[nodiscard]] auto empty(this array const &self) -> bool
+ {
+ return self.size() == 0;
+ }
+
+ /*
+ * Reserve space for future insertions.
+ */
+ auto reserve(this array &self, size_type nelems) -> void
+ {
+ ::ucl_object_reserve(self.get_ucl_object(), nelems);
+ }
+
+ /*
+ * Append an element to the array.
+ */
+ auto push_back(this array &self, value_type const &v) -> void
+ {
+ auto uobj = ::ucl_object_ref(v.get_ucl_object());
+ ::ucl_array_append(self.get_ucl_object(), uobj);
+ }
+
+ /*
+ * Prepend an element to the array.
+ */
+ auto push_front(this array &self, value_type const &v) -> void
+ {
+ auto uobj = ::ucl_object_ref(v.get_ucl_object());
+ ::ucl_array_prepend(self.get_ucl_object(), uobj);
+ }
+
+ /*
+ * Access an array element by index.
+ */
+ [[nodiscard]] auto at(this array const &self, size_type idx) -> T
+ {
+ if (idx >= self.size())
+ throw std::out_of_range("UCL array index out of range");
+
+ auto uobj = ::ucl_array_find_index(self.get_ucl_object(), idx);
+ if (uobj == nullptr)
+ throw std::runtime_error(
+ "failed to fetch UCL array index");
+
+ return T(nihil::ucl::ref, uobj);
+ }
+
+ [[nodiscard]] auto operator[] (this array const &self, size_type idx) -> T
+ {
+ return self.at(idx);
+ }
+
+ /*
+ * Return the first element.
+ */
+ [[nodiscard]] auto front(this array const &self) -> T
+ {
+ return self.at(0);
+ }
+
+ /*
+ * Return the last element.
+ */
+ [[nodiscard]] auto back(this array const &self) -> T
+ {
+ if (self.empty())
+ throw std::out_of_range("attempt to access back() on "
+ "empty UCL array");
+ return self.at(self.size() - 1);
+ }
+};
+
+/*
+ * Comparison operators.
+ */
+
+export template<datatype T> [[nodiscard]]
+auto operator==(array<T> const &a, array<T> const &b) -> bool
+{
+ if (a.size() != b.size())
+ return false;
+
+ for (typename array<T>::size_type i = 0; i < a.size(); ++i)
+ if (a.at(i) != b.at(i))
+ return false;
+
+ return true;
+}
+
+/*
+ * Print an array to an ostream; uses the same format as std::format().
+ */
+export template<datatype T>
+auto operator<<(std::ostream &strm, array<T> const &a) -> std::ostream &
+{
+ return strm << std::format("{}", a);
+}
+
+} // namespace nihil::ucl
+
+/*
+ * std::formatter for an array. The output format is a list of values
+ * on a single line: [1, 2, 3].
+ */
+export template<typename T>
+struct std::formatter<nihil::ucl::array<T>, char>
+{
+ template<class ParseContext>
+ constexpr ParseContext::iterator parse(ParseContext& ctx)
+ {
+ return ctx.begin();
+ }
+
+ template<class FmtContext>
+ FmtContext::iterator format(nihil::ucl::array<T> const &o,
+ FmtContext& ctx) const
+ {
+ auto it = ctx.out();
+ bool first = true;
+
+ *it++ = '[';
+
+ for (auto &&elm : o) {
+ if (first)
+ first = false;
+ else {
+ *it++ = ',';
+ *it++ = ' ';
+ }
+
+ it = std::format_to(it, "{}", elm);
+ }
+
+ *it++ = ']';
+ return it;
+ }
+};