/* * This source code is released into the public domain. */ module; #include #include #include #include #include #include #include #include #include #include export module nihil.ucl:array; import :object; namespace nihil::ucl { export template struct array; export template 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; ::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 [[nodiscard]] auto operator+(array_iterator const &lhs, typename array_iterator::difference_type rhs) -> array_iterator { auto copy = lhs; copy += rhs; return copy; } export template [[nodiscard]] auto operator+(typename array_iterator::difference_type lhs, array_iterator const &rhs) -> array_iterator { return rhs - lhs; } export template [[nodiscard]] auto operator-(array_iterator const &lhs, typename array_iterator::difference_type rhs) -> array_iterator { auto copy = lhs; copy -= rhs; return copy; } export template 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; /* * 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( ::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( ::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 requires(std::convertible_to, 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 requires(std::convertible_to, 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 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 [[nodiscard]] auto operator==(array const &a, array const &b) -> bool { if (a.size() != b.size()) return false; for (typename array::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 auto operator<<(std::ostream &strm, array 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 struct std::formatter, char> { template constexpr ParseContext::iterator parse(ParseContext& ctx) { return ctx.begin(); } template FmtContext::iterator format(nihil::ucl::array 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; } };