/* * This source code is released into the public domain. */ module; #include #include #include #include #include #include #include #include #include export module nihil.ucl:map; import :object; namespace nihil::ucl { // Exception thrown when map::operator[] does not find the key. export struct key_not_found : error { key_not_found(std::string_view key) : error(std::format("key '{}' not found in map", key)) , m_key(key) {} auto key(this key_not_found const &self) -> std::string_view { return self.m_key; } private: std::string m_key; }; export template struct map; template struct map_iterator { using difference_type = std::ptrdiff_t; using value_type = std::pair; using reference = value_type &; using const_reference = value_type const &; using pointer = value_type *; using const_pointer = value_type const *; struct sentinel{}; [[nodiscard]] auto operator==(this map_iterator const &self, sentinel) -> bool { return (self.m_state->cur == nullptr); } auto operator++(this map_iterator &self) -> map_iterator & { self.m_state->next(); return self; } auto operator++(this map_iterator &self, int) -> map_iterator & { self.m_state->next(); return self; } [[nodiscard]] auto operator*(this map_iterator const &self) -> value_type { auto obj = T(ref, self.m_state->cur); return {obj.key(), std::move(obj)}; } private: friend struct map; map_iterator(::ucl_object_t const *obj) : m_state(std::make_shared(obj)) { ++(*this); } struct state { state(::ucl_object_t const *obj) { iter = ::ucl_object_iterate_new(obj); if (iter == nullptr) throw std::system_error(make_error_code( std::errc(errno))); } state(state const &) = delete; auto operator=(this state &, state const &) -> state& = delete; ~state() { if (iter != nullptr) ::ucl_object_iterate_free(iter); } auto next() -> void { cur = ::ucl_object_iterate_safe(iter, true); } ucl_object_iter_t iter = nullptr; ucl_object_t const *cur = nullptr; }; std::shared_ptr m_state; }; export template struct map final : object { inline static constexpr object_type ucl_type = object_type::object; using value_type = std::pair; using size_type = std::size_t; using difference_type = std::ptrdiff_t; using iterator = map_iterator; /* * Create an empty map. Throws std::system_error on failure. */ map() : object(noref, [] { auto *uobj = ::ucl_object_typed_new(UCL_OBJECT); if (uobj == nullptr) throw std::system_error( std::make_error_code(std::errc(errno))); return uobj; }()) { } /* * Create a map 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. */ map(ref_t, ::ucl_object_t const *uobj) : object(nihil::ucl::ref, [&] { auto actual_type = static_cast( ::ucl_object_type(uobj)); if (actual_type != map::ucl_type) throw type_mismatch(map::ucl_type, actual_type); return uobj; }()) { if (type() != ucl_type) throw type_mismatch(ucl_type, type()); } map(noref_t, ::ucl_object_t *uobj) : object(nihil::ucl::noref, [&] { auto actual_type = static_cast( ::ucl_object_type(uobj)); if (actual_type != map::ucl_type) throw type_mismatch(map::ucl_type, actual_type); return uobj; }()) { } /* * Create a map from an iterator pair. */ template requires(std::convertible_to, value_type>) map(Iterator first, Iterator last) : map() { // This is exception safe, because if we throw here the // base class destructor will free the map. while (first != last) { insert(*first); ++first; } } /* * Create a map from a range. */ template requires(std::convertible_to, value_type>) map(std::from_range_t, Range &&range) : map(std::ranges::begin(range), std::ranges::end(range)) { } /* * Create a map from an initializer_list. */ map(std::initializer_list const &list) : map(std::ranges::begin(list), std::ranges::end(list)) { } /* * Map iterator access. */ [[nodiscard]] auto begin(this map const &self) -> iterator { return {self.get_ucl_object()}; } [[nodiscard]] auto end(this map const &) -> iterator::sentinel { return {}; } /* * Reserve space for future insertions. */ auto reserve(this map &self, size_type nelems) -> void { ::ucl_object_reserve(self.get_ucl_object(), nelems); } /* * Add an element to the map. */ auto insert(this map &self, value_type const &v) -> void { auto uobj = ::ucl_object_ref(v.second.get_ucl_object()); ::ucl_object_insert_key(self.get_ucl_object(), uobj, v.first.data(), v.first.size(), true); } /* * Access a map element by key. */ [[nodiscard]] auto find(this map const &self, std::string_view key) -> std::optional { auto const *obj = ::ucl_object_lookup_len( self.get_ucl_object(), key.data(), key.size()); if (obj == nullptr) return {}; return {T(nihil::ucl::ref, obj)}; } /* * Remove an object from the map. */ auto remove(this map &self, std::string_view key) -> bool { return ::ucl_object_delete_keyl(self.get_ucl_object(), key.data(), key.size()); } /* * Remove an object from the map and return it. */ auto pop(this map &self, std::string_view key) -> std::optional { auto *uobj = ::ucl_object_pop_keyl(self.get_ucl_object(), key.data(), key.size()); if (uobj) return T(noref, uobj); return {}; } /* * Equivalent to find(), except it throws key_not_found if the key * doesn't exist in the map. */ [[nodiscard]] auto operator[] (this map const &self, std::string_view key) -> T { auto obj = self.find(key); if (obj) return *obj; throw key_not_found(key); } }; } // namespace nihil::ucl