// This source code is released into the public domain. module; #include export module nihil.ucl:map; import nihil.std; import :object; namespace nihil::ucl { // Exception thrown when map::operator[] does not find the key. export struct key_not_found : error { explicit 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; // The map iterator. UCL doesn't provide a way to copy an iterator, so this is an // input iterator: it can only go forwards. 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->current_object() == nullptr; } auto operator++(this map_iterator &self) -> map_iterator & { self.m_state->advance(); return self; } auto operator++(this map_iterator &self, int) -> map_iterator & { self.m_state->advance(); return self; } [[nodiscard]] auto operator*(this map_iterator const &self) -> value_type { auto *cur = self.m_state->current_object(); if (cur == nullptr) throw std::logic_error("map_iterator::operator* called on end()"); auto obj = T(ref, cur); return {obj.key(), std::move(obj)}; } private: friend struct map; explicit map_iterator(::ucl_object_t const *obj) : m_state(std::make_shared(obj)) { ++(*this); } struct state { explicit state(::ucl_object_t const *obj) : m_ucl_iterator([obj] { if (auto *iter = ::ucl_object_iterate_new(obj); iter != nullptr) return iter; throw std::system_error(make_error_code(sys_error())); }()) { } state(state const &) = delete; auto operator=(state const &) -> state & = delete; state(state &&) = delete; auto operator=(state &&) -> state & = delete; ~state() { if (m_ucl_iterator != nullptr) ::ucl_object_iterate_free(m_ucl_iterator); } auto advance(this state &self) -> void { self.m_current_object = ::ucl_object_iterate_safe(self.m_ucl_iterator, true); } auto current_object(this state const &self) -> ::ucl_object_t const * { return self.m_current_object; } private: ucl_object_iter_t m_ucl_iterator = nullptr; ucl_object_t const *m_current_object = nullptr; }; std::shared_ptr m_state; }; export template struct map final : object { static constexpr auto 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; using sentinel = typename iterator::sentinel; ~map() override = default; // 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(sys_error())); 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 * const uobj) : object(nihil::ucl::ref, ensure_ucl_type(uobj, map::ucl_type)) { } map(noref_t, ::ucl_object_t * const uobj) : object(nihil::ucl::noref, ensure_ucl_type(uobj, map::ucl_type)) { } // Create a map from a range of value types. template requires(std::convertible_to, value_type>) explicit map(Range const &range) : map() { // This is exception safe, because if we throw here the // base class destructor will free the map. for (auto &&v: range) insert(v); } // Create a map from an iterator pair. template requires(std::convertible_to, value_type>) map(Iterator first, Iterator last) : map(std::ranges::subrange(first, last)) { } // Create a map from an initializer_list. map(std::initializer_list const &list) : map(std::ranges::subrange(std::ranges::begin(list), std::ranges::end(list))) { } // Copyable. Note that this copies the entire UCL object. map(map const &) = default; auto operator=(map const &) -> map & = default; // Movable. map(map &&) = default; auto operator=(map &&other) -> map & = default; // // Map iterator access. // [[nodiscard]] auto begin(this map const &self) -> iterator { return iterator(self.get_ucl_object()); } [[nodiscard]] auto end(this map const &) -> sentinel { return {}; } // Reserve space for future insertions. auto reserve(this map &self, size_type const 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 const 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 const key) -> bool { return ::ucl_object_delete_keyl(self.get_ucl_object(), key.data(), key.size()); } // Remove an object from the map and return it. If the map is empty, returns nullopt. auto pop(this map &self, std::string_view const 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 const key) -> T { if (auto obj = self.find(key); obj ) return *obj; throw key_not_found(key); } }; } // namespace nihil::ucl