diff options
Diffstat (limited to 'nihil.ucl/array.ccm')
| -rw-r--r-- | nihil.ucl/array.ccm | 342 |
1 files changed, 342 insertions, 0 deletions
diff --git a/nihil.ucl/array.ccm b/nihil.ucl/array.ccm new file mode 100644 index 0000000..26cd9b9 --- /dev/null +++ b/nihil.ucl/array.ccm @@ -0,0 +1,342 @@ +/* + * This source code is released into the public domain. + */ + +module; + +#include <cassert> +#include <cstdint> +#include <cstdlib> +#include <string> + +#include <ucl.h> + +export module nihil.ucl:array; + +import :object; + +namespace nihil::ucl { + +export template<datatype T> +struct array; + +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; + + auto operator* (this array_iterator const &self) -> T + { + auto uobj = ::ucl_array_find_index(self._array, self._idx); + if (uobj == nullptr) + throw error("failed to fetch UCL array index"); + + return T(::ucl_object_ref(uobj)); + } + + auto operator[] (this array_iterator const &self, + difference_type idx) + -> T + { + return *(self + idx); + } + + auto operator++ (this array_iterator &self) -> array_iterator& + { + ++self._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._idx == 0) + throw std::out_of_range("attempt to iterate before " + "start of UCL array"); + --self._idx; + return self; + } + + auto operator-- (this array_iterator &self, int) -> array_iterator + { + auto copy = self; + --self; + return copy; + } + + auto operator== (this array_iterator const &lhs, + array_iterator const &rhs) + -> bool + { + return lhs._idx == rhs._idx; + } + + auto operator<=> (this array_iterator const &lhs, + array_iterator const &rhs) + { + return lhs._idx <=> rhs._idx; + } + + auto operator+= (this array_iterator &lhs, + difference_type rhs) + -> array_iterator & + { + lhs._idx += rhs; + return lhs; + } + + auto operator-= (this array_iterator &lhs, + difference_type rhs) + -> array_iterator & + { + lhs._idx -= rhs; + return lhs; + } + + auto operator- (this array_iterator const &lhs, + array_iterator const &rhs) + -> difference_type + { + return lhs._idx - rhs._idx; + } + +private: + friend struct array<T>; + + ::ucl_object_t const *_array{}; + std::size_t _idx{}; + + array_iterator(::ucl_object_t const *array, std::size_t idx) + : _array(array) + , _idx(idx) + {} +}; + +export template<datatype T> +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> +auto operator+(typename array_iterator<T>::difference_type lhs, + array_iterator<T> const &rhs) + -> array_iterator<T> +{ + return rhs - lhs; +} + +export template<datatype T> +auto operator-(array_iterator<T> const &lhs, + typename array_iterator<T>::difference_type rhs) + -> array_iterator<T> +{ + auto copy = lhs; + copy -= rhs; + return copy; +} + +static_assert(std::random_access_iterator<array_iterator<object>>); + +export template<datatype T> +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; + + array() : object(::ucl_object_typed_new(UCL_ARRAY)) + { + if (_object == nullptr) + throw error("failed to create UCL object"); + } + + explicit array(::ucl_object_t *uobj) : object(uobj) + { + assert(type() == object_type::array); + } + + /* + * 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) + : object(::ucl_object_typed_new(UCL_ARRAY)) + { + if (_object == nullptr) + throw error("failed to create UCL object"); + + // 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. + */ + + auto begin(this array const &self) -> array_iterator<T> + { + return {self.get_ucl_object(), 0}; + } + + auto end(this array const &self) -> array_iterator<T> + { + return {self.get_ucl_object(), self.size()}; + } + + /* + * Return the size of this array. + */ + auto size(this array const &self) -> size_type + { + return ::ucl_array_size(self.get_ucl_object()); + } + + /* + * Test if this array is empty. + */ + 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 &&v) -> void + { + // There's no real benefit to moving the object here, but + // move it anyway to preserve the expected semantics. + auto copy = std::move(v); + self.push_back(copy); + } + + 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 &&v) -> void + { + // There's no real benefit to moving the object here, but + // move it anyway to preserve the expected semantics. + auto copy = std::move(v); + self.push_front(copy); + } + + 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. + */ + 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 error("failed to fetch UCL array index"); + + return T(::ucl_object_ref(uobj)); + } + + auto operator[] (this array const &self, size_type idx) -> T + { + return self.at(idx); + } + + /* + * Return the first element. + */ + auto front(this array const &self) -> T + { + return self.at(0); + } + + /* + * Return the last element. + */ + 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> +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; +} + +} // namespace nihil::ucl |
