diff options
Diffstat (limited to 'nihil.ucl')
| -rw-r--r-- | nihil.ucl/CMakeLists.txt | 34 | ||||
| -rw-r--r-- | nihil.ucl/array.ccm | 424 | ||||
| -rw-r--r-- | nihil.ucl/array.test.cc (renamed from nihil.ucl/tests/array.cc) | 141 | ||||
| -rw-r--r-- | nihil.ucl/boolean.cc | 106 | ||||
| -rw-r--r-- | nihil.ucl/boolean.ccm | 138 | ||||
| -rw-r--r-- | nihil.ucl/boolean.test.cc (renamed from nihil.ucl/tests/boolean.cc) | 99 | ||||
| -rw-r--r-- | nihil.ucl/emit.cc | 21 | ||||
| -rw-r--r-- | nihil.ucl/emit.ccm | 179 | ||||
| -rw-r--r-- | nihil.ucl/emit.test.cc (renamed from nihil.ucl/tests/emit.cc) | 11 | ||||
| -rw-r--r-- | nihil.ucl/errc.cc | 49 | ||||
| -rw-r--r-- | nihil.ucl/errc.ccm | 33 | ||||
| -rw-r--r-- | nihil.ucl/integer.cc | 102 | ||||
| -rw-r--r-- | nihil.ucl/integer.ccm | 165 | ||||
| -rw-r--r-- | nihil.ucl/integer.test.cc (renamed from nihil.ucl/tests/integer.cc) | 21 | ||||
| -rw-r--r-- | nihil.ucl/map.ccm | 263 | ||||
| -rw-r--r-- | nihil.ucl/map.test.cc (renamed from nihil.ucl/tests/map.cc) | 56 | ||||
| -rw-r--r-- | nihil.ucl/nihil.ucl.ccm | 8 | ||||
| -rw-r--r-- | nihil.ucl/object.cc | 114 | ||||
| -rw-r--r-- | nihil.ucl/object.ccm | 182 | ||||
| -rw-r--r-- | nihil.ucl/object.test.cc (renamed from nihil.ucl/tests/object.cc) | 5 | ||||
| -rw-r--r-- | nihil.ucl/object_cast.ccm | 19 | ||||
| -rw-r--r-- | nihil.ucl/parse.test.cc (renamed from nihil.ucl/tests/parse.cc) | 10 | ||||
| -rw-r--r-- | nihil.ucl/parser.cc | 102 | ||||
| -rw-r--r-- | nihil.ucl/parser.ccm | 158 | ||||
| -rw-r--r-- | nihil.ucl/real.cc | 104 | ||||
| -rw-r--r-- | nihil.ucl/real.ccm | 151 | ||||
| -rw-r--r-- | nihil.ucl/real.test.cc (renamed from nihil.ucl/tests/real.cc) | 67 | ||||
| -rw-r--r-- | nihil.ucl/string.cc | 187 | ||||
| -rw-r--r-- | nihil.ucl/string.ccm | 357 | ||||
| -rw-r--r-- | nihil.ucl/string.test.cc (renamed from nihil.ucl/tests/string.cc) | 74 | ||||
| -rw-r--r-- | nihil.ucl/tests/CMakeLists.txt | 20 | ||||
| -rw-r--r-- | nihil.ucl/type.cc | 62 | ||||
| -rw-r--r-- | nihil.ucl/type.ccm | 87 |
33 files changed, 1416 insertions, 2133 deletions
diff --git a/nihil.ucl/CMakeLists.txt b/nihil.ucl/CMakeLists.txt index 9d8ab3a..5b8ed72 100644 --- a/nihil.ucl/CMakeLists.txt +++ b/nihil.ucl/CMakeLists.txt @@ -3,13 +3,12 @@ pkg_check_modules(LIBUCL REQUIRED libucl) add_library(nihil.ucl STATIC) -target_link_libraries(nihil.ucl PRIVATE nihil.error nihil.monad) +target_link_libraries(nihil.ucl PRIVATE nihil.std nihil.core nihil.error nihil.monad) target_sources(nihil.ucl PUBLIC FILE_SET modules TYPE CXX_MODULES FILES nihil.ucl.ccm emit.ccm - errc.ccm object.ccm object_cast.ccm parser.ccm @@ -21,18 +20,6 @@ target_sources(nihil.ucl map.ccm real.ccm string.ccm - - PRIVATE - emit.cc - errc.cc - parser.cc - type.cc - - object.cc - boolean.cc - integer.cc - real.cc - string.cc ) target_compile_options(nihil.ucl PUBLIC ${LIBUCL_CFLAGS_OTHER}) @@ -41,6 +28,23 @@ target_link_libraries(nihil.ucl PUBLIC ${LIBUCL_LIBRARIES}) target_link_directories(nihil.ucl PUBLIC ${LIBUCL_LIBRARY_DIRS}) if(NIHIL_TESTS) - add_subdirectory(tests) + add_executable(nihil.ucl.test + array.test.cc + boolean.test.cc + emit.test.cc + integer.test.cc + map.test.cc + object.test.cc + parse.test.cc + real.test.cc + string.test.cc + ) + + target_link_libraries(nihil.ucl.test PRIVATE nihil.ucl Catch2::Catch2WithMain) + + include(CTest) + include(Catch) + catch_discover_tests(nihil.ucl.test) + enable_testing() endif() diff --git a/nihil.ucl/array.ccm b/nihil.ucl/array.ccm index e3730ab..3d211d5 100644 --- a/nihil.ucl/array.ccm +++ b/nihil.ucl/array.ccm @@ -1,165 +1,119 @@ -/* - * This source code is released into the public domain. - */ - +// 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 nihil.std; import :object; namespace nihil::ucl { -export template<datatype T> +export template <datatype T> struct array; -export template<datatype T> -struct array_iterator { +// +// The array iterator. This is hardened, i.e. it always checks bounds. +// +export template <datatype T> +struct array_iterator +{ using difference_type = std::ptrdiff_t; using value_type = T; - using reference = T&; - using pointer = T*; + using reference = T &; + using pointer = T *; array_iterator() = default; - [[nodiscard]] auto operator* (this array_iterator const &self) -> T + // Return the value at this position. We don't do type checking here + // since we assume that was done when the array was created or cast. + [[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"); + 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"); + throw std::runtime_error("nihil::ucl::array_iterator: " + "failed to fetch UCL array index"); - return T(nihil::ucl::ref, uobj); + return T(ref, uobj); } - [[nodiscard]] auto operator[] (this array_iterator const &self, - difference_type idx) - -> T + // Return the value at an offset. + [[nodiscard]] auto operator[](this array_iterator const &self, difference_type idx) -> T { return *(self + idx); } - auto operator++ (this array_iterator &self) -> array_iterator & + // Advance this iterator. + auto operator++(this array_iterator &self) -> array_iterator & { auto arr = self.get_array(); + // If we're already at end, don't allow going any further. if (self.m_idx == ::ucl_array_size(arr)) - throw std::logic_error( - "nihil::ucl::array_iterator: " - "iterating past end of array"); + 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 operator++(this array_iterator &self, int) -> array_iterator { auto copy = self; ++self; return copy; } - auto operator-- (this array_iterator &self) -> array_iterator& + 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"); + 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 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 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"); + 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 & + 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"); + 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 + [[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"); + throw std::logic_error("nihil::ucl::array_iterator: " + "comparing iterators of different arrays"); return lhs.m_idx - rhs.m_idx; } @@ -167,146 +121,148 @@ struct array_iterator { private: friend struct array<T>; - ::ucl_object_t const * m_array{}; - std::size_t m_idx{}; + ::ucl_object_t const *m_array{}; + std::size_t m_idx{}; - [[nodiscard]] auto get_array(this array_iterator const &self) - -> ::ucl_object_t const * + [[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"); + 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) + + array_iterator(::ucl_object_t const *array, std::size_t const 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; -} + [[nodiscard]] friend auto + operator==(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; -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; -} + if (lhs.get_array() != rhs.get_array()) + throw std::logic_error("nihil::ucl::array_iterator: " + "comparing iterators of different arrays"); -export template<datatype T = object> -struct array final : object { - inline static constexpr object_type ucl_type = object_type::array; + return lhs.m_idx == rhs.m_idx; + } + + [[nodiscard]] friend auto operator<=>(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; + } + + [[nodiscard]] friend auto + operator+(array_iterator const &lhs, difference_type rhs) -> array_iterator + { + auto copy = lhs; + copy += rhs; + return copy; + } + + [[nodiscard]] friend auto + operator+(difference_type lhs, array_iterator const &rhs) -> array_iterator + { + return rhs - lhs; + } + + [[nodiscard]] friend auto + operator-(array_iterator const &lhs, difference_type rhs) -> array_iterator + { + auto copy = lhs; + copy -= rhs; + return copy; + } +}; + +export template <datatype T = object> +struct array final : object +{ + 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, [] { + // Movable. + array(array &&) noexcept = default; + auto operator=(array &&) noexcept -> array & = default; + + // Copyable. Note that this copies the entire UCL object. + array(array const &) = default; + auto operator=(array const &) -> array & = default; + + ~array() override = default; + + // 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))); + throw std::system_error(std::make_error_code(sys_error())); 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. - */ + // 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; - }()) + : object(nihil::ucl::ref, ensure_ucl_type(uobj, array::ucl_type)) { } 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; - }()) + : object(nihil::ucl::noref, ensure_ucl_type(uobj, array::ucl_type)) { } - /* - * 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() + // Create an array from a range. + template <std::ranges::range Range> + requires(std::convertible_to<std::ranges::range_value_t<Range>, T>) + explicit array(Range const &range) + : 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; - } + for (auto &&elm : range) + push_back(elm); } - /* - * 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 iterator pair. + template <std::input_iterator Iterator> + requires(std::convertible_to<std::iter_value_t<Iterator>, T>) + array(Iterator first, Iterator last) + : array(std::ranges::subrange(first, last)) { } - /* - * Create an array from an initializer_list. - */ + // Create an array from an initializer_list. array(std::initializer_list<T> const &list) - : array(std::ranges::begin(list), - std::ranges::end(list)) + : array(std::ranges::subrange(list)) { } - /* - * Array iterator access. - */ + // + // Array iterator access. + // [[nodiscard]] auto begin(this array const &self) -> iterator { @@ -318,133 +274,111 @@ struct array final : object { return {self.get_ucl_object(), self.size()}; } - /* - * Return the size of this array. - */ + // 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. - */ + // 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 + // Reserve space for future insertions. + auto reserve(this array &self, size_type const nelems) -> void { ::ucl_object_reserve(self.get_ucl_object(), nelems); } - /* - * Append an element to the array. - */ + // 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. - */ + // 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 + // Access an array element by index. + [[nodiscard]] auto at(this array const &self, size_type const 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"); + 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 + [[nodiscard]] auto operator[](this array const &self, size_type const idx) -> T { return self.at(idx); } - /* - * Return the first element. - */ + // Return the first element. [[nodiscard]] auto front(this array const &self) -> T { return self.at(0); } - /* - * Return the last element. - */ + // 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"); + "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; +private: + // + // Comparison operators. + // - for (typename array<T>::size_type i = 0; i < a.size(); ++i) - if (a.at(i) != b.at(i)) + [[nodiscard]] friend auto operator==(array const &a, array const &b) -> bool + { + if (a.size() != b.size()) return false; - return true; -} + for (size_type i = 0; i < a.size(); ++i) + if (a.at(i) != b.at(i)) + return false; -/* - * 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); -} + return true; + } + + // Print an array to an ostream; uses the same format as std::format(). + friend 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<typename T> +// 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) + template <class ParseContext> + constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator { + (void)this; return ctx.begin(); } - template<class FmtContext> - FmtContext::iterator format(nihil::ucl::array<T> const &o, - FmtContext& ctx) const + template <class FmtContext> + auto format(nihil::ucl::array<T> const &o, FmtContext &ctx) const -> FmtContext::iterator { auto it = ctx.out(); bool first = true; diff --git a/nihil.ucl/tests/array.cc b/nihil.ucl/array.test.cc index 866fa45..89394a0 100644 --- a/nihil.ucl/tests/array.cc +++ b/nihil.ucl/array.test.cc @@ -1,18 +1,12 @@ -/* - * This source code is released into the public domain. - */ - -#include <algorithm> -#include <concepts> -#include <expected> -#include <ranges> -#include <string> +// This source code is released into the public domain. #include <catch2/catch_test_macros.hpp> #include <ucl.h> +import nihil.std; import nihil.ucl; +namespace { TEST_CASE("ucl: array: invariants", "[ucl]") { using namespace nihil::ucl; @@ -27,42 +21,44 @@ TEST_CASE("ucl: array: invariants", "[ucl]") static_assert(std::equality_comparable<array<>>); static_assert(std::totally_ordered<array<>>); static_assert(std::swappable<array<>>); - + static_assert(std::ranges::sized_range<array<integer>>); - static_assert(std::same_as<std::ranges::range_value_t<array<integer>>, - integer>); + static_assert(std::same_as<std::ranges::range_value_t<array<integer>>, integer>); } TEST_CASE("ucl: array: constructor", "[ucl]") { using namespace nihil::ucl; - SECTION("default") { + SECTION("default") + { auto arr = array<integer>(); REQUIRE(arr.size() == 0); REQUIRE(str(arr.type()) == "array"); } - SECTION("from range") { + SECTION("from range") + { auto vec = std::vector{integer(1), integer(42)}; - auto arr = array<integer>(std::from_range, vec); + auto arr = array<integer>(vec); REQUIRE(arr.size() == 2); REQUIRE(arr[0] == 1); REQUIRE(arr[1] == 42); } - SECTION("from iterator pair") { + SECTION("from iterator pair") + { auto vec = std::vector{integer(1), integer(42)}; - auto arr = array<integer>(std::ranges::begin(vec), - std::ranges::end(vec)); + auto arr = array<integer>(std::ranges::begin(vec), std::ranges::end(vec)); REQUIRE(arr.size() == 2); REQUIRE(arr[0] == 1); REQUIRE(arr[1] == 42); } - SECTION("from initializer_list") { + SECTION("from initializer_list") + { auto arr = array<integer>{integer(1), integer(42)}; REQUIRE(arr.size() == 2); @@ -75,9 +71,10 @@ TEST_CASE("ucl: array: construct from UCL object", "[ucl]") { using namespace nihil::ucl; - SECTION("ref, correct type") { - auto uarr = ::ucl_object_typed_new(UCL_ARRAY); - auto uint = ::ucl_object_fromint(42); + SECTION("ref, correct type") + { + auto *uarr = ::ucl_object_typed_new(UCL_ARRAY); + auto *uint = ::ucl_object_fromint(42); ::ucl_array_append(uarr, uint); auto arr = array<integer>(ref, uarr); @@ -86,34 +83,38 @@ TEST_CASE("ucl: array: construct from UCL object", "[ucl]") ::ucl_object_unref(uarr); } - SECTION("noref, correct type") { - auto uarr = ::ucl_object_typed_new(UCL_ARRAY); - auto uint = ::ucl_object_fromint(42); + SECTION("noref, correct type") + { + auto *uarr = ::ucl_object_typed_new(UCL_ARRAY); + auto *uint = ::ucl_object_fromint(42); ::ucl_array_append(uarr, uint); auto arr = array<integer>(noref, uarr); REQUIRE(arr[0] == 42); } - SECTION("ref, wrong element type") { - auto uarr = ::ucl_object_typed_new(UCL_ARRAY); - auto uint = ::ucl_object_frombool(true); + SECTION("ref, wrong element type") + { + auto *uarr = ::ucl_object_typed_new(UCL_ARRAY); + auto *uint = ::ucl_object_frombool(true); ::ucl_array_append(uarr, uint); auto arr = array<integer>(noref, uarr); REQUIRE_THROWS_AS(arr[0], type_mismatch); } - SECTION("ref, wrong type") { - auto uobj = ::ucl_object_frombool(true); + SECTION("ref, wrong type") + { + auto *uobj = ::ucl_object_frombool(true); REQUIRE_THROWS_AS(array(ref, uobj), type_mismatch); ::ucl_object_unref(uobj); } - SECTION("noref, wrong type") { - auto uobj = ::ucl_object_frombool(true); + SECTION("noref, wrong type") + { + auto *uobj = ::ucl_object_frombool(true); REQUIRE_THROWS_AS(array(noref, uobj), type_mismatch); @@ -125,10 +126,8 @@ TEST_CASE("ucl: array: swap", "[ucl]") { // do not add using namespace nihil::ucl - auto arr1 = nihil::ucl::array<nihil::ucl::integer>{ - nihil::ucl::integer(1), - nihil::ucl::integer(2) - }; + auto arr1 = nihil::ucl::array<nihil::ucl::integer>{nihil::ucl::integer(1), + nihil::ucl::integer(2)}; auto arr2 = nihil::ucl::array<nihil::ucl::integer>{ nihil::ucl::integer(3), @@ -169,9 +168,7 @@ TEST_CASE("ucl: array: compare", "[ucl]") { using namespace nihil::ucl; - auto arr = array<integer>{ - integer(1), integer(42), integer(666) - }; + auto arr = array<integer>{integer(1), integer(42), integer(666)}; auto arr2 = array<integer>(); REQUIRE(arr != arr2); @@ -181,9 +178,7 @@ TEST_CASE("ucl: array: compare", "[ucl]") arr2.push_back(integer(666)); REQUIRE(arr == arr2); - auto arr3 = array<integer>{ - integer(1), integer(1), integer(1) - }; + auto arr3 = array<integer>{integer(1), integer(1), integer(1)}; REQUIRE(arr != arr3); } @@ -237,33 +232,33 @@ TEST_CASE("ucl: array: emit", "[ucl]") auto ucl = parse("array = [1, 42, 666];").value(); auto output = std::format("{:c}", ucl); - REQUIRE(output == -"array [\n" -" 1,\n" -" 42,\n" -" 666,\n" -"]\n"); + REQUIRE(output == "array [\n" + " 1,\n" + " 42,\n" + " 666,\n" + "]\n"); } TEST_CASE("ucl: array: format", "[ucl]") { using namespace nihil::ucl; - SECTION("empty array") { + SECTION("empty array") + { auto arr = array<integer>(); REQUIRE(std::format("{}", arr) == "[]"); } - SECTION("bare array") { - auto arr = array<integer>{ - integer(1), integer(42), integer(666) - }; + SECTION("bare array") + { + auto arr = array<integer>{integer(1), integer(42), integer(666)}; auto output = std::format("{}", arr); REQUIRE(output == "[1, 42, 666]"); } - SECTION("parsed array") { + SECTION("parsed array") + { auto ucl = parse("array = [1, 42, 666];").value(); auto arr = object_cast<array<integer>>(ucl["array"]).value(); @@ -276,7 +271,8 @@ TEST_CASE("ucl: array: print to ostream", "[ucl]") { using namespace nihil::ucl; - SECTION("empty array") { + SECTION("empty array") + { auto arr = array<integer>(); auto strm = std::ostringstream(); strm << arr; @@ -284,17 +280,17 @@ TEST_CASE("ucl: array: print to ostream", "[ucl]") REQUIRE(strm.str() == "[]"); } - SECTION("bare array") { - auto arr = array<integer>{ - integer(1), integer(42), integer(666) - }; + SECTION("bare array") + { + auto arr = array<integer>{integer(1), integer(42), integer(666)}; auto strm = std::ostringstream(); strm << arr; REQUIRE(strm.str() == "[1, 42, 666]"); } - SECTION("parsed array") { + SECTION("parsed array") + { auto ucl = parse("array = [1, 42, 666];").value(); auto arr = object_cast<array<integer>>(ucl["array"]).value(); auto strm = std::ostringstream(); @@ -325,12 +321,10 @@ TEST_CASE("ucl: array is a sized_range", "[ucl]") std::ranges::copy(arr, std::back_inserter(vec)); REQUIRE(std::ranges::equal(arr, vec)); - auto arr_as_ints = - arr | std::views::transform(&integer::value); + auto arr_as_ints = arr | std::views::transform(&integer::value); auto int_vec = std::vector<integer::contained_type>(); std::ranges::copy(arr_as_ints, std::back_inserter(int_vec)); REQUIRE(int_vec == std::vector<std::int64_t>{1, 42, 666}); - } TEST_CASE("ucl: array: bad object_cast", "[ucl]") @@ -435,16 +429,18 @@ TEST_CASE("array iterator: invalid operations", "[ucl]") { using namespace nihil::ucl; - auto arr = array<integer>{ integer(42) }; + auto arr = array<integer>{integer(42)}; auto it = arr.begin(); - SECTION("decrement before start") { + SECTION("decrement before start") + { REQUIRE_THROWS_AS(--it, std::logic_error); REQUIRE_THROWS_AS(it--, std::logic_error); REQUIRE_THROWS_AS(it - 1, std::logic_error); } - SECTION("increment past end") { + SECTION("increment past end") + { ++it; REQUIRE(it == arr.end()); @@ -453,7 +449,8 @@ TEST_CASE("array iterator: invalid operations", "[ucl]") REQUIRE_THROWS_AS(it + 1, std::logic_error); } - SECTION("dereference iterator at end") { + SECTION("dereference iterator at end") + { REQUIRE_THROWS_AS(it[1], std::logic_error); ++it; @@ -462,17 +459,21 @@ TEST_CASE("array iterator: invalid operations", "[ucl]") REQUIRE_THROWS_AS(*it, std::logic_error); } - SECTION("compare with different array") { - auto arr2 = array<integer>{ integer(42) }; + SECTION("compare with different array") + { + auto arr2 = array<integer>{integer(42)}; REQUIRE_THROWS_AS(it == arr2.begin(), std::logic_error); REQUIRE_THROWS_AS(it > arr2.begin(), std::logic_error); REQUIRE_THROWS_AS(it - arr2.begin(), std::logic_error); } - SECTION("compare with empty iterator") { + SECTION("compare with empty iterator") + { auto it2 = array_iterator<integer>(); REQUIRE_THROWS_AS(it == it2, std::logic_error); REQUIRE_THROWS_AS(it > it2, std::logic_error); REQUIRE_THROWS_AS(it - it2, std::logic_error); } } + +} // anonymous namespace diff --git a/nihil.ucl/boolean.cc b/nihil.ucl/boolean.cc deleted file mode 100644 index 91f2b17..0000000 --- a/nihil.ucl/boolean.cc +++ /dev/null @@ -1,106 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include <compare> -#include <cstdlib> -#include <expected> -#include <system_error> - -#include <ucl.h> - -module nihil.ucl; - -import nihil.error; - -namespace nihil::ucl { - -auto make_boolean(boolean::contained_type value) - -> std::expected<boolean, error> -{ - auto *uobj = ::ucl_object_frombool(value); - if (uobj == nullptr) - return std::unexpected(error( - errc::failed_to_create_object, - error(std::errc(errno)))); - - return boolean(noref, uobj); -} - -boolean::boolean() - : boolean(false) -{ -} - -boolean::boolean(contained_type value) - : object(noref, [&] { - auto *uobj = ::ucl_object_frombool(value); - if (uobj == nullptr) - throw std::system_error( - std::make_error_code(std::errc(errno))); - return uobj; - }()) -{ -} - -boolean::boolean(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 != boolean::ucl_type) - throw type_mismatch(boolean::ucl_type, actual_type); - return uobj; - }()) -{ -} - -boolean::boolean(noref_t, ::ucl_object_t *uobj) - : object(nihil::ucl::noref, [&] { - auto actual_type = static_cast<object_type>( - ::ucl_object_type(uobj)); - if (actual_type != boolean::ucl_type) - throw type_mismatch(boolean::ucl_type, actual_type); - return uobj; - }()) -{ -} - -auto boolean::value(this boolean const &self) - -> contained_type -{ - auto v = contained_type{}; - auto const *uobj = self.get_ucl_object(); - - if (::ucl_object_toboolean_safe(uobj, &v)) - return v; - - std::abort(); -} - -auto operator== (boolean const &a, boolean const &b) - -> bool -{ - return a.value() == b.value(); -} - -auto operator<=> (boolean const &a, boolean const &b) - -> std::strong_ordering -{ - return a.value() <=> b.value(); -} - -auto operator== (boolean const &a, boolean::contained_type b) - -> bool -{ - return a.value() == b; -} - -auto operator<=> (boolean const &a, boolean::contained_type b) - -> std::strong_ordering -{ - return a.value() <=> b; -} - -} // namespace nihil::ucl diff --git a/nihil.ucl/boolean.ccm b/nihil.ucl/boolean.ccm index 068dfdd..4cacdc4 100644 --- a/nihil.ucl/boolean.ccm +++ b/nihil.ucl/boolean.ccm @@ -1,90 +1,118 @@ -/* - * This source code is released into the public domain. - */ - +// This source code is released into the public domain. module; -#include <cassert> -#include <cstdint> -#include <cstdlib> -#include <expected> -#include <format> -#include <string> - #include <ucl.h> export module nihil.ucl:boolean; +import nihil.std; +import nihil.core; import :object; namespace nihil::ucl { -export struct boolean final : object { +export struct boolean final : object +{ using contained_type = bool; - inline static constexpr object_type ucl_type = object_type::boolean; + static constexpr object_type ucl_type = object_type::boolean; + + // Create a boolean holding the value false. Throws std::system_error + // on failure. + boolean() + : boolean(false) + { + } + + // Create a boolean holding a specific value. Throws std::system_error + // on failure. + explicit boolean(bool const value) + : object(noref, [&] { + auto *uobj = ::ucl_object_frombool(value); + if (uobj == nullptr) + throw std::system_error(std::make_error_code(sys_error())); + return uobj; + }()) + { + } - /* - * Create a boolean holding the value false. Throws std::system_error - * on failure. - */ - boolean(); + // Create a new boolean from a UCL object. Throws type_mismatch + // on failure. - /* - * Create a boolean holding a specific value. Throws std::system_error - * on failure. - */ - explicit boolean(bool); + boolean(ref_t, ::ucl_object_t const *uobj) + : object(nihil::ucl::ref, ensure_ucl_type(uobj, boolean::ucl_type)) + { + } - /* - * Create a new boolean from a UCL object. Throws type_mismatch - * on failure. - */ - boolean(ref_t, ::ucl_object_t const *uobj); - boolean(noref_t, ::ucl_object_t *uobj); + boolean(noref_t, ::ucl_object_t *uobj) + : object(nihil::ucl::noref, ensure_ucl_type(uobj, boolean::ucl_type)) + { + } // Return this object's value. - auto value(this boolean const &self) -> contained_type; -}; + auto value(this boolean const &self) -> contained_type + { + auto v = contained_type{}; + auto const *uobj = self.get_ucl_object(); -/* - * Boolean constructors. These return an error instead of throwing. - */ + if (::ucl_object_toboolean_safe(uobj, &v)) + return v; -export [[nodiscard]] auto -make_boolean(boolean::contained_type = false) -> std::expected<boolean, error>; + throw std::runtime_error("ucl_object_toboolean_safe failed"); + } + +private: + // Comparison operators. + [[nodiscard]] friend auto operator==(boolean const &a, boolean const &b) -> bool + { + return a.value() == b.value(); + } -/* - * Comparison operators. - */ + [[nodiscard]] friend auto + operator<=>(boolean const &a, boolean const &b) -> std::strong_ordering + { + return static_cast<int>(a.value()) <=> static_cast<int>(b.value()); + } -export auto operator== (boolean const &a, boolean const &b) -> bool; -export auto operator== (boolean const &a, boolean::contained_type b) -> bool; -export auto operator<=> (boolean const &a, boolean const &b) - -> std::strong_ordering; -export auto operator<=> (boolean const &a, boolean::contained_type b) - -> std::strong_ordering; + [[nodiscard]] friend auto operator==(boolean const &a, contained_type const b) -> bool + { + return a.value() == b; + } + + [[nodiscard]] friend auto + operator<=>(boolean const &a, contained_type const b) -> std::strong_ordering + { + return static_cast<int>(a.value()) <=> static_cast<int>(b); + } +}; + +// Boolean constructors. This returns an error instead of throwing. +export [[nodiscard]] auto +make_boolean(boolean::contained_type const value = false) -> std::expected<boolean, error> +{ + if (auto *uobj = ::ucl_object_frombool(value); uobj == nullptr) + return error(errc::failed_to_create_object, error(sys_error())); + else + return boolean(noref, uobj); +} } // namespace nihil::ucl -/* - * std::formatter for a boolean. This provides the same format operations - * as std::formatter<bool>. - */ -export template<> +// std::formatter for a boolean. This provides the same format operations +// as std::formatter<bool>. +export template <> struct std::formatter<nihil::ucl::boolean, char> { std::formatter<bool> base_formatter; - template<class ParseContext> - constexpr ParseContext::iterator parse(ParseContext& ctx) + template <class ParseContext> + constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator { return base_formatter.parse(ctx); } - template<class FmtContext> - FmtContext::iterator format(nihil::ucl::boolean const &o, - FmtContext& ctx) const + template <class FmtContext> + auto format(nihil::ucl::boolean const &o, FmtContext &ctx) const -> FmtContext::iterator { return base_formatter.format(o.value(), ctx); } diff --git a/nihil.ucl/tests/boolean.cc b/nihil.ucl/boolean.test.cc index f7ef95e..9fb0148 100644 --- a/nihil.ucl/tests/boolean.cc +++ b/nihil.ucl/boolean.test.cc @@ -1,16 +1,15 @@ -/* - * This source code is released into the public domain. - */ - -#include <concepts> -#include <string> +// This source code is released into the public domain. #include <catch2/catch_test_macros.hpp> #include <ucl.h> +import nihil.std; import nihil.ucl; -TEST_CASE("ucl: boolean: invariants", "[ucl]") +namespace { +inline auto constexpr *test_tags = "[nihil][nihil.ucl][nihil.ucl.boolean]"; + +TEST_CASE("ucl: boolean: invariants", test_tags) { using namespace nihil::ucl; @@ -27,18 +26,24 @@ TEST_CASE("ucl: boolean: invariants", "[ucl]") static_assert(std::swappable<boolean>); } -TEST_CASE("ucl: boolean: constructor", "[ucl]") +SCENARIO("Constructing a ucl::boolean", test_tags) { using namespace nihil::ucl; - SECTION("default") { + GIVEN ("A default-constructed boolean") { auto b = boolean(); - REQUIRE(b == false); + + THEN ("The value is false") { + REQUIRE(b == false); + } } - SECTION("with value") { + GIVEN ("A boolean constructed from a true value") { auto b = boolean(true); - REQUIRE(b == true); + + THEN ("The value is true") { + REQUIRE(b == true); + } } } @@ -46,8 +51,9 @@ TEST_CASE("ucl: boolean: construct from UCL object", "[ucl]") { using namespace nihil::ucl; - SECTION("ref, correct type") { - auto uobj = ::ucl_object_frombool(true); + SECTION("ref, correct type") + { + auto *uobj = ::ucl_object_frombool(true); auto i = boolean(ref, uobj); REQUIRE(i == true); @@ -55,23 +61,26 @@ TEST_CASE("ucl: boolean: construct from UCL object", "[ucl]") ::ucl_object_unref(uobj); } - SECTION("noref, correct type") { - auto uobj = ::ucl_object_frombool(true); + SECTION("noref, correct type") + { + auto *uobj = ::ucl_object_frombool(true); auto i = boolean(noref, uobj); REQUIRE(i == true); } - SECTION("ref, wrong type") { - auto uobj = ::ucl_object_fromint(1); + SECTION("ref, wrong type") + { + auto *uobj = ::ucl_object_fromint(1); REQUIRE_THROWS_AS(boolean(ref, uobj), type_mismatch); ::ucl_object_unref(uobj); } - SECTION("noref, wrong type") { - auto uobj = ::ucl_object_fromint(1); + SECTION("noref, wrong type") + { + auto *uobj = ::ucl_object_fromint(1); REQUIRE_THROWS_AS(boolean(noref, uobj), type_mismatch); @@ -83,12 +92,14 @@ TEST_CASE("ucl: boolean: make_boolean", "[ucl]") { using namespace nihil::ucl; - SECTION("default value") { + SECTION("default value") + { auto b = make_boolean().value(); REQUIRE(b == false); } - SECTION("explicit value") { + SECTION("explicit value") + { auto b = make_boolean(true).value(); REQUIRE(b == true); } @@ -97,7 +108,7 @@ TEST_CASE("ucl: boolean: make_boolean", "[ucl]") TEST_CASE("ucl: boolean: swap", "[ucl]") { // do not add using namespace nihil::ucl - + auto b1 = nihil::ucl::boolean(true); auto b2 = nihil::ucl::boolean(false); @@ -117,14 +128,11 @@ TEST_CASE("ucl: boolean: key()", "[ucl]") { using namespace nihil::ucl; - auto err = parse("a_bool = true"); - REQUIRE(err); - - auto obj = *err; - REQUIRE(object_cast<boolean>(obj["a_bool"])->key() == "a_bool"); + auto obj = parse("a_bool = true").value(); + REQUIRE(object_cast<boolean>(obj["a_bool"]).value().key() == "a_bool"); - auto b = nihil::ucl::boolean(true); - REQUIRE(b.key() == ""); + auto b = boolean(true); + REQUIRE(b.key().empty() == true); } TEST_CASE("ucl: boolean: comparison", "[ucl]") @@ -133,22 +141,26 @@ TEST_CASE("ucl: boolean: comparison", "[ucl]") auto b = boolean(true); - SECTION("operator==") { + SECTION("operator==") + { REQUIRE(b == true); REQUIRE(b == boolean(true)); } - SECTION("operator!=") { + SECTION("operator!=") + { REQUIRE(b != false); REQUIRE(b != boolean(false)); } - SECTION("operator<") { + SECTION("operator<") + { REQUIRE(b <= true); REQUIRE(b <= nihil::ucl::boolean(true)); } - SECTION("operator>") { + SECTION("operator>") + { REQUIRE(b > false); REQUIRE(b > nihil::ucl::boolean(false)); } @@ -172,8 +184,7 @@ TEST_CASE("ucl: boolean: parse and emit", "[ucl]") auto ucl = parse("bool = true;").value(); auto output = std::string(); - emit(ucl, nihil::ucl::emitter::configuration, - std::back_inserter(output)); + emit(ucl, nihil::ucl::emitter::configuration, std::back_inserter(output)); REQUIRE(output == "bool = true;\n"); } @@ -182,12 +193,14 @@ TEST_CASE("ucl: boolean: format", "[ucl]") { using namespace nihil::ucl; - SECTION("bare boolean") { + SECTION("bare boolean") + { auto str = std::format("{}", boolean(true)); REQUIRE(str == "true"); } - SECTION("parsed boolean") { + SECTION("parsed boolean") + { auto obj = parse("bool = true;").value(); auto b = object_cast<boolean>(obj["bool"]).value(); @@ -195,7 +208,8 @@ TEST_CASE("ucl: boolean: format", "[ucl]") REQUIRE(str == "true"); } - SECTION("with format string") { + SECTION("with format string") + { auto str = std::format("{: >5}", boolean(true)); REQUIRE(str == " true"); } @@ -205,14 +219,16 @@ TEST_CASE("ucl: boolean: print to ostream", "[ucl]") { using namespace nihil::ucl; - SECTION("bare boolean") { + SECTION("bare boolean") + { auto strm = std::ostringstream(); strm << boolean(true); REQUIRE(strm.str() == "true"); } - SECTION("parsed boolean") { + SECTION("parsed boolean") + { auto obj = parse("bool = true;").value(); auto i = object_cast<boolean>(obj["bool"]).value(); @@ -222,3 +238,4 @@ TEST_CASE("ucl: boolean: print to ostream", "[ucl]") REQUIRE(strm.str() == "true"); } } +} // anonymous namespace diff --git a/nihil.ucl/emit.cc b/nihil.ucl/emit.cc deleted file mode 100644 index 480ddd8..0000000 --- a/nihil.ucl/emit.cc +++ /dev/null @@ -1,21 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include <iostream> -#include <iterator> - -module nihil.ucl; - -namespace nihil::ucl { - -auto operator<<(std::ostream &stream, object const &o) --> std::ostream & -{ - emit(o, emitter::json, std::ostream_iterator<char>(stream)); - return stream; -} - -} // namespace nihil::ucl diff --git a/nihil.ucl/emit.ccm b/nihil.ucl/emit.ccm index b88f8e7..64b8f4f 100644 --- a/nihil.ucl/emit.ccm +++ b/nihil.ucl/emit.ccm @@ -1,110 +1,83 @@ -/* - * This source code is released into the public domain. - */ - +// This source code is released into the public domain. module; -#include <array> -#include <charconv> -#include <cstdlib> -#include <format> -#include <iterator> -#include <iosfwd> -#include <span> -#include <string> -#include <utility> - #include <ucl.h> export module nihil.ucl:emit; +import nihil.std; import :object; namespace nihil::ucl { -export enum struct emitter { +export enum struct emitter : std::uint8_t { configuration = UCL_EMIT_CONFIG, compact_json = UCL_EMIT_JSON_COMPACT, json = UCL_EMIT_JSON, yaml = UCL_EMIT_YAML, }; -/* - * Wrap ucl_emitter_functions for a particular output iterator type. - * - * We can't throw exceptions here since we're called from C code. The emit - * functions return an integer value, but it's not really clear what this is - * for and the C API seems to mostly ignore it. So, we just eat errors and - * keep going. - */ -template<std::output_iterator<char> Iterator> -struct emit_wrapper { - emit_wrapper(Iterator iterator_) - : iterator(std::move(iterator_)) - {} - - static auto append_character(unsigned char c, std::size_t nchars, - void *ud) - noexcept -> int - try { - auto *self = static_cast<emit_wrapper *>(ud); - - while (nchars--) - *self->iterator++ = static_cast<char>(c); +// Wrap ucl_emitter_functions for a particular output iterator type. +// +// We can't throw exceptions here since we're called from C code. The emit +// functions return an integer value, but it's not really clear what this is +// for and the C API seems to mostly ignore it. So, we just eat errors and +// keep going. +template <std::output_iterator<char> Iterator> +struct emit_wrapper +{ + explicit emit_wrapper(Iterator iterator) + : m_iterator(std::move(iterator)) + { + } + static auto + append_character(unsigned char const c, std::size_t nchars, void *const ud) noexcept -> int + try { + auto &self = check_magic(ud); + self.m_iterator = + std::ranges::fill_n(self.m_iterator, nchars, static_cast<char>(c)); return 0; } catch (...) { return 0; } - static auto append_len(unsigned char const *str, std::size_t len, - void *ud) - noexcept -> int + static auto append_len(unsigned char const *const str, std::size_t const len, + void *const ud) noexcept -> int try { - auto *self = static_cast<emit_wrapper *>(ud); - - for (auto c : std::span(str, len)) - *self->iterator++ = static_cast<char>(c); - + auto &self = check_magic(ud); + self.m_iterator = std::ranges::copy(std::span(str, len), self.m_iterator).out; return 0; } catch (...) { return 0; } - static auto append_int(std::int64_t value, void *ud) - noexcept -> int + static auto append_int(std::int64_t const value, void *const ud) noexcept -> int try { - auto constexpr bufsize = - std::numeric_limits<std::int64_t>::digits10; - auto buf = std::array<char, bufsize>(); + auto &self = check_magic(ud); - auto *self = static_cast<emit_wrapper *>(ud); - auto result = std::to_chars(buf.data(), buf.data() + buf.size(), - value, 10); - - if (result.ec == std::errc()) - for (auto c : std::span(buf.data(), result.ptr)) - *self->iterator++ = c; + auto buf = std::array<char, std::numeric_limits<std::int64_t>::digits10>(); + auto result = std::to_chars(begin(buf), end(buf), value, 10); + if (result.ec == std::errc()) { + auto chars = std::span(buf.data(), result.ptr); + self.m_iterator = std::ranges::copy(chars, self.m_iterator).out; + } return 0; } catch (...) { return 0; } - static auto append_double(double value, void *ud) - noexcept -> int + static auto append_double(double const value, void *const ud) noexcept -> int try { - auto constexpr bufsize = - std::numeric_limits<double>::digits10; - auto buf = std::array<char, bufsize>(); - - auto *self = static_cast<emit_wrapper *>(ud); - auto result = std::to_chars(buf.data(), buf.data() + buf.size(), - value); + auto &self = check_magic(ud); - if (result.ec == std::errc()) - for (auto c : std::span(buf.data(), result.ptr)) - *self->iterator++ = c; + auto buf = std::array<char, std::numeric_limits<double>::digits10>(); + auto result = std::to_chars(begin(buf), end(buf), value); + if (result.ec == std::errc()) { + auto chars = std::span(buf.data(), result.ptr); + self.m_iterator = std::ranges::copy(chars, self.m_iterator).out; + } return 0; } catch (...) { @@ -124,40 +97,61 @@ struct emit_wrapper { return ret; } + [[nodiscard]] auto iterator(this emit_wrapper &self) -> Iterator & + { + return self.m_iterator; + } + + [[nodiscard]] auto iterator(this emit_wrapper const &self) -> Iterator const & + { + return self.m_iterator; + } + private: - Iterator iterator{}; + Iterator m_iterator{}; + std::uint64_t m_magic = wrapper_magic; + + // Harden against memory errors. + static constexpr auto wrapper_magic = std::uint32_t{0x57524150}; + + static auto check_magic(void *p) -> emit_wrapper & + { + auto *ret = static_cast<emit_wrapper *>(p); + if (ret->m_magic != wrapper_magic) + throw std::runtime_error("Invalid emit_wrapper pointer"); + return *ret; + } }; -export auto emit(object const &object, emitter format, - std::output_iterator<char> auto &&it) - -> void +export auto +emit(object const &object, emitter const format, std::output_iterator<char> auto &&it) -> void { auto ucl_format = static_cast<ucl_emitter>(format); auto wrapper = emit_wrapper(it); auto functions = wrapper.get_functions(); - ::ucl_object_emit_full(object.get_ucl_object(), ucl_format, - &functions, nullptr); + ::ucl_object_emit_full(object.get_ucl_object(), ucl_format, &functions, nullptr); } -/* - * Basic ostream printer for UCL; default to JSON since it's probably what - * most people expect. - */ -export auto operator<<(std::ostream &, object const &) -> std::ostream &; +// Basic ostream printer for UCL; default to JSON since it's probably what +// most people expect. Note that most derived UCL types override this. +export auto operator<<(std::ostream &stream, object const &o) -> std::ostream & +{ + emit(o, emitter::json, std::ostream_iterator<char>(stream)); + return stream; +} } // namespace nihil::ucl -/* - * Specialisation of std::formatter<> for object. - */ -template<std::derived_from<nihil::ucl::object> T> +// Specialisation of std::formatter<> for object. Note that most derived +// UCL types override this. +template <std::derived_from<nihil::ucl::object> T> struct std::formatter<T, char> { nihil::ucl::emitter emitter = nihil::ucl::emitter::json; - template<class ParseContext> - constexpr ParseContext::iterator parse(ParseContext& ctx) + template <class ParseContext> + constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator { auto it = ctx.begin(); auto end = ctx.end(); @@ -179,8 +173,7 @@ struct std::formatter<T, char> case '}': return it; default: - throw std::format_error("Invalid format string " - "for UCL object"); + throw std::format_error("Invalid format string for UCL object"); } ++it; @@ -189,9 +182,8 @@ struct std::formatter<T, char> return it; } - template<class FmtContext> - FmtContext::iterator format(nihil::ucl::object const &o, - FmtContext& ctx) const + template <class FmtContext> + auto format(nihil::ucl::object const &o, FmtContext &ctx) const -> FmtContext::iterator { // We can't use emit() here since the context iterator is not // an std::output_iterator. @@ -202,8 +194,7 @@ struct std::formatter<T, char> auto wrapper = nihil::ucl::emit_wrapper(out); auto functions = wrapper.get_functions(); - ::ucl_object_emit_full(o.get_ucl_object(), ucl_format, - &functions, nullptr); - return out; + ::ucl_object_emit_full(o.get_ucl_object(), ucl_format, &functions, nullptr); + return wrapper.iterator(); } }; diff --git a/nihil.ucl/tests/emit.cc b/nihil.ucl/emit.test.cc index a7dcd71..51c4e0e 100644 --- a/nihil.ucl/tests/emit.cc +++ b/nihil.ucl/emit.test.cc @@ -1,14 +1,11 @@ -/* - * This source code is released into the public domain. - */ - -#include <format> -#include <sstream> +// This source code is released into the public domain. #include <catch2/catch_test_macros.hpp> +import nihil.std; import nihil.ucl; +namespace { TEST_CASE("ucl: emit to std::ostream", "[ucl]") { using namespace std::literals; @@ -91,3 +88,5 @@ TEST_CASE("ucl: emit YAML with std::format", "[ucl]") " 666\n" "]"); } + +} // anonymous namespace diff --git a/nihil.ucl/errc.cc b/nihil.ucl/errc.cc deleted file mode 100644 index 0b65b86..0000000 --- a/nihil.ucl/errc.cc +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include <string> -#include <system_error> - -module nihil.ucl; - -namespace nihil::ucl { - -struct ucl_error_category final : std::error_category { - auto name() const noexcept -> char const * override; - auto message(int err) const -> std::string override; -}; - -auto ucl_category() noexcept -> std::error_category & -{ - static auto category = ucl_error_category(); - return category; -} - -auto make_error_condition(errc ec) -> std::error_condition -{ - return {static_cast<int>(ec), ucl_category()}; -} - -auto ucl_error_category::name() const noexcept -> char const * -{ - return "nihil.ucl"; -} - -auto ucl_error_category::message(int err) const -> std::string -{ - switch (static_cast<errc>(err)) { - case errc::no_error: - return "No error"; - case errc::failed_to_create_object: - return "Failed to create UCL object"; - case errc::type_mismatch: - return "UCL type does not match expected type"; - default: - return "Undefined error"; - } -} - -} // namespace nihil::ucl diff --git a/nihil.ucl/errc.ccm b/nihil.ucl/errc.ccm deleted file mode 100644 index 8f0444d..0000000 --- a/nihil.ucl/errc.ccm +++ /dev/null @@ -1,33 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include <string> -#include <system_error> - -export module nihil.ucl:errc; - -namespace nihil::ucl { - -export enum struct errc { - no_error = 0, - - // ucl_object_new() or similar failed, e.g. out of memory - failed_to_create_object, - // Trying to create an object from a UCL object of the wrong type - type_mismatch, -}; - -export auto ucl_category() noexcept -> std::error_category &; -export auto make_error_condition(errc ec) -> std::error_condition; - -} // namespace nihil::ucl - -namespace std { - -export template<> -struct is_error_condition_enum<nihil::ucl::errc> : true_type {}; - -} // namespace std diff --git a/nihil.ucl/integer.cc b/nihil.ucl/integer.cc deleted file mode 100644 index 825d8f6..0000000 --- a/nihil.ucl/integer.cc +++ /dev/null @@ -1,102 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include <compare> -#include <cstdlib> -#include <expected> -#include <system_error> - -#include <ucl.h> - -module nihil.ucl; - -import nihil.error; - -namespace nihil::ucl { - -integer::integer() - : integer(0) -{ -} - -integer::integer(contained_type value) - : integer(noref, [&] { - auto *uobj = ::ucl_object_fromint(value); - if (uobj == nullptr) - throw std::system_error( - std::make_error_code(std::errc(errno))); - return uobj; - }()) -{ -} - -integer::integer(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 != integer::ucl_type) - throw type_mismatch(integer::ucl_type, actual_type); - return uobj; - }()) -{ -} - -integer::integer(noref_t, ::ucl_object_t *uobj) - : object(nihil::ucl::noref, [&] { - auto actual_type = static_cast<object_type>( - ::ucl_object_type(uobj)); - if (actual_type != integer::ucl_type) - throw type_mismatch(integer::ucl_type, actual_type); - return uobj; - }()) -{ -} - -auto make_integer(integer::contained_type value) - -> std::expected<integer, error> -{ - auto *uobj = ::ucl_object_fromint(value); - if (uobj == nullptr) - return std::unexpected(error( - errc::failed_to_create_object, - error(std::errc(errno)))); - - return integer(noref, uobj); -} - -auto integer::value(this integer const &self) -> contained_type -{ - auto v = contained_type{}; - auto const *uobj = self.get_ucl_object(); - - if (::ucl_object_toint_safe(uobj, &v)) - return v; - - std::abort(); -} - -auto operator== (integer const &a, integer const &b) -> bool -{ - return a.value() == b.value(); -} - -auto operator<=> (integer const &a, integer const &b) -> std::strong_ordering -{ - return a.value() <=> b.value(); -} - -auto operator== (integer const &a, integer::contained_type b) -> bool -{ - return a.value() == b; -} - -auto operator<=> (integer const &a, integer::contained_type b) - -> std::strong_ordering -{ - return a.value() <=> b; -} - -} // namespace nihil::ucl diff --git a/nihil.ucl/integer.ccm b/nihil.ucl/integer.ccm index e35a471..eb7fa6b 100644 --- a/nihil.ucl/integer.ccm +++ b/nihil.ucl/integer.ccm @@ -1,114 +1,139 @@ -/* - * This source code is released into the public domain. - */ - +// This source code is released into the public domain. module; -#include <compare> -#include <cstdint> -#include <cstdlib> -#include <expected> -#include <format> -#include <utility> - #include <ucl.h> export module nihil.ucl:integer; +import nihil.std; +import nihil.core; +import nihil.error; import :object; import :type; namespace nihil::ucl { -export struct integer final : object { +export struct integer final : object +{ using contained_type = std::int64_t; - inline static constexpr object_type ucl_type = object_type::integer; - - /* - * Create an integer holding the value 0. Throws std::system_error - * on failure. - */ - integer(); - - /* - * Create an integer holding a specific value. Throws std::system_error - * on failure. - */ - explicit integer(contained_type value); - - /* - * Create a new integer from a UCL object. Throws type_mismatch - * on failure. - */ - integer(ref_t, ::ucl_object_t const *uobj); - integer(noref_t, ::ucl_object_t *uobj); + static constexpr object_type ucl_type = object_type::integer; + + // Create an integer holding the value 0. Throws std::system_error + // on failure. + integer() + : integer(0) + { + } + + // Create an integer holding a specific value. Throws std::system_error + // on failure. + explicit integer(contained_type value) + : integer(noref, [&] { + auto *uobj = ::ucl_object_fromint(value); + if (uobj == nullptr) + throw std::system_error(std::make_error_code(sys_error())); + return uobj; + }()) + { + } + + // Create a new integer from a UCL object. Throws type_mismatch + // on failure. + integer(ref_t, ::ucl_object_t const *uobj) + : object(nihil::ucl::ref, ensure_ucl_type(uobj, integer::ucl_type)) + { + } + + integer(noref_t, ::ucl_object_t *uobj) + : object(nihil::ucl::noref, ensure_ucl_type(uobj, integer::ucl_type)) + { + } // Return the value of this object. - [[nodiscard]] auto value(this integer const &self) -> contained_type; -}; + [[nodiscard]] auto value(this integer const &self) -> contained_type + { + auto v = contained_type{}; + auto const *uobj = self.get_ucl_object(); -/* - * Integer constructors. These return an error instead of throwing. - */ + if (::ucl_object_toint_safe(uobj, &v)) + return v; -export [[nodiscard]] auto -make_integer(integer::contained_type = 0) -> std::expected<integer, error>; + throw std::runtime_error("ucl_object_toint_safe failed"); + } -/* - * Comparison operators. - */ +private: + // + // Comparison operators. + // -export [[nodiscard]] auto operator== (integer const &a, - integer const &b) -> bool; + [[nodiscard]] friend auto operator==(integer const &a, integer const &b) -> bool + { + return a.value() == b.value(); + } -export [[nodiscard]] auto operator== (integer const &a, - integer::contained_type b) -> bool; + [[nodiscard]] friend auto operator==(integer const &a, integer::contained_type b) -> bool + { + return a.value() == b; + } -export [[nodiscard]] auto operator<=> (integer const &a, - integer const &b) - -> std::strong_ordering; + [[nodiscard]] friend auto + operator<=>(integer const &a, integer const &b) -> std::strong_ordering + { + return a.value() <=> b.value(); + } -export [[nodiscard]] auto operator<=> (integer const &a, - integer::contained_type b) - -> std::strong_ordering; + [[nodiscard]] friend auto + operator<=>(integer const &a, integer::contained_type b) -> std::strong_ordering + { + return a.value() <=> b; + } +}; + +// Integer constructors. This returns an error instead of throwing. +export [[nodiscard]] auto +make_integer(integer::contained_type value = 0) -> std::expected<integer, error> +{ + auto *uobj = ::ucl_object_fromint(value); + if (uobj == nullptr) + return error(errc::failed_to_create_object, error(sys_error())); + + return integer(noref, uobj); +} -/* - * Literal operator. - */ +// Literal operator for integers. inline namespace literals { -export constexpr auto operator""_ucl (unsigned long long i) -> integer +export constexpr auto operator""_ucl(unsigned long long i) -> integer { if (std::cmp_greater(i, std::numeric_limits<std::int64_t>::max())) throw std::out_of_range("literal out of range"); return integer(static_cast<std::int64_t>(i)); } -} // namespace nihil::ucl::literals +} // namespace literals } // namespace nihil::ucl -namespace nihil { inline namespace literals { - export using namespace ::nihil::ucl::literals; -}} // namespace nihil::literals +namespace nihil { +inline namespace literals { +export using namespace ::nihil::ucl::literals; +} // namespace literals +} // namespace nihil -/* - * std::formatter for an integer. This provides the same format operations - * as std::formatter<std::int64_t>. - */ -export template<> +// std::formatter for an integer. This provides the same format operations +// as std::formatter<std::int64_t>. +export template <> struct std::formatter<nihil::ucl::integer, char> { std::formatter<std::int64_t> base_formatter; - template<class ParseContext> - constexpr ParseContext::iterator parse(ParseContext& ctx) + template <class ParseContext> + constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator { return base_formatter.parse(ctx); } - template<class FmtContext> - FmtContext::iterator format(nihil::ucl::integer const &o, - FmtContext& ctx) const + template <class FmtContext> + auto format(nihil::ucl::integer const &o, FmtContext &ctx) const -> FmtContext::iterator { return base_formatter.format(o.value(), ctx); } diff --git a/nihil.ucl/tests/integer.cc b/nihil.ucl/integer.test.cc index 6584764..68567e9 100644 --- a/nihil.ucl/tests/integer.cc +++ b/nihil.ucl/integer.test.cc @@ -1,16 +1,12 @@ -/* - * This source code is released into the public domain. - */ - -#include <concepts> -#include <cstdint> -#include <string> +// This source code is released into the public domain. #include <catch2/catch_test_macros.hpp> #include <ucl.h> +import nihil.std; import nihil.ucl; +namespace { TEST_CASE("ucl: integer: invariants", "[ucl]") { using namespace nihil::ucl; @@ -67,7 +63,7 @@ TEST_CASE("ucl: integer: construct from UCL object", "[ucl]") using namespace nihil::ucl; SECTION("ref, correct type") { - auto uobj = ::ucl_object_fromint(42); + auto *uobj = ::ucl_object_fromint(42); auto i = integer(ref, uobj); REQUIRE(i == 42); @@ -76,14 +72,14 @@ TEST_CASE("ucl: integer: construct from UCL object", "[ucl]") } SECTION("noref, correct type") { - auto uobj = ::ucl_object_fromint(42); + auto *uobj = ::ucl_object_fromint(42); auto i = integer(noref, uobj); REQUIRE(i == 42); } SECTION("ref, wrong type") { - auto uobj = ::ucl_object_frombool(true); + auto *uobj = ::ucl_object_frombool(true); REQUIRE_THROWS_AS(integer(ref, uobj), type_mismatch); @@ -91,7 +87,7 @@ TEST_CASE("ucl: integer: construct from UCL object", "[ucl]") } SECTION("noref, wrong type") { - auto uobj = ::ucl_object_frombool(true); + auto *uobj = ::ucl_object_frombool(true); REQUIRE_THROWS_AS(integer(noref, uobj), type_mismatch); @@ -147,7 +143,7 @@ TEST_CASE("ucl: integer: key()", "[ucl]") SECTION("bare integer, no key") { auto i = 42_ucl; - REQUIRE(i.key() == ""); + REQUIRE(i.key().empty() == true); } } @@ -245,3 +241,4 @@ TEST_CASE("ucl: integer: print to ostream", "[ucl]") REQUIRE(strm.str() == "42"); } } +} // anonymous namespace diff --git a/nihil.ucl/map.ccm b/nihil.ucl/map.ccm index fa77601..73ef583 100644 --- a/nihil.ucl/map.ccm +++ b/nihil.ucl/map.ccm @@ -1,47 +1,41 @@ -/* - * This source code is released into the public domain. - */ - +// This source code is released into the public domain. module; -#include <cassert> -#include <cstdint> -#include <cstdlib> -#include <format> -#include <memory> -#include <optional> -#include <string> -#include <system_error> - #include <ucl.h> 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 { - key_not_found(std::string_view 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<datatype T> +export template <datatype T> struct map; -template<datatype T> -struct map_iterator { +// 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 <datatype T> +struct map_iterator +{ using difference_type = std::ptrdiff_t; using value_type = std::pair<std::string_view, T>; using reference = value_type &; @@ -49,242 +43,219 @@ struct map_iterator { using pointer = value_type *; using const_pointer = value_type const *; - struct sentinel{}; + struct sentinel + { + }; - [[nodiscard]] auto operator==(this map_iterator const &self, sentinel) - -> bool + [[nodiscard]] auto operator==(this map_iterator const &self, sentinel) -> bool { - return (self.m_state->cur == nullptr); + return self.m_state->current_object() == nullptr; } auto operator++(this map_iterator &self) -> map_iterator & { - self.m_state->next(); + self.m_state->advance(); return self; } auto operator++(this map_iterator &self, int) -> map_iterator & { - self.m_state->next(); + self.m_state->advance(); return self; } - [[nodiscard]] auto operator*(this map_iterator const &self) - -> value_type + [[nodiscard]] auto operator*(this map_iterator const &self) -> value_type { - auto obj = T(ref, self.m_state->cur); + 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<T>; - map_iterator(::ucl_object_t const *obj) + explicit map_iterator(::ucl_object_t const *obj) : m_state(std::make_shared<state>(obj)) { ++(*this); } - struct state { - state(::ucl_object_t const *obj) + 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())); + }()) { - 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; + auto operator=(state const &) -> state & = delete; + + state(state &&) = delete; + auto operator=(state &&) -> state & = delete; ~state() { - if (iter != nullptr) - ::ucl_object_iterate_free(iter); + 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 next() -> void + auto current_object(this state const &self) -> ::ucl_object_t const * { - cur = ::ucl_object_iterate_safe(iter, true); + return self.m_current_object; } - ucl_object_iter_t iter = nullptr; - ucl_object_t const *cur = nullptr; + private: + ucl_object_iter_t m_ucl_iterator = nullptr; + ucl_object_t const *m_current_object = nullptr; }; std::shared_ptr<state> m_state; }; -export template<datatype T = object> -struct map final : object { - inline static constexpr object_type ucl_type = object_type::object; +export template <datatype T = object> +struct map final : object +{ + static constexpr auto ucl_type = object_type::object; using value_type = std::pair<std::string_view, T>; using size_type = std::size_t; using difference_type = std::ptrdiff_t; using iterator = map_iterator<T>; + using sentinel = typename iterator::sentinel; - /* - * Create an empty map. Throws std::system_error on failure. - */ - map() : object(noref, [] { + ~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(std::errc(errno))); + 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 *uobj) - : object(nihil::ucl::ref, [&] { - auto actual_type = static_cast<object_type>( - ::ucl_object_type(uobj)); - if (actual_type != map::ucl_type) - throw type_mismatch(map::ucl_type, - actual_type); - 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)) { - 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<object_type>( - ::ucl_object_type(uobj)); - if (actual_type != map::ucl_type) - throw type_mismatch(map::ucl_type, - actual_type); - return uobj; - }()) + map(noref_t, ::ucl_object_t * const uobj) + : object(nihil::ucl::noref, ensure_ucl_type(uobj, map::ucl_type)) { } - /* - * Create a map from an iterator pair. - */ - template<std::input_iterator Iterator> - requires(std::convertible_to<std::iter_value_t<Iterator>, value_type>) - map(Iterator first, Iterator last) - : map() + // Create a map from a range of value types. + template <std::ranges::range Range> + requires(std::convertible_to<std::ranges::range_value_t<Range>, 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. - while (first != last) { - insert(*first); - ++first; - } + for (auto &&v: range) + insert(v); } - /* - * Create a map from a range. - */ - template<std::ranges::range Range> - requires(std::convertible_to<std::ranges::range_value_t<Range>, - value_type>) - map(std::from_range_t, Range &&range) - : map(std::ranges::begin(range), - std::ranges::end(range)) + // Create a map from an iterator pair. + template <std::input_iterator Iterator> + requires(std::convertible_to<std::iter_value_t<Iterator>, value_type>) + map(Iterator first, Iterator last) + : map(std::ranges::subrange(first, last)) { } - /* - * Create a map from an initializer_list. - */ + // Create a map from an initializer_list. map(std::initializer_list<value_type> const &list) - : map(std::ranges::begin(list), std::ranges::end(list)) + : map(std::ranges::subrange(std::ranges::begin(list), std::ranges::end(list))) { } - /* - * Map iterator access. - */ + // 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 {self.get_ucl_object()}; + return iterator(self.get_ucl_object()); } - [[nodiscard]] auto end(this map const &) -> iterator::sentinel + [[nodiscard]] auto end(this map const &) -> sentinel { return {}; } - /* - * Reserve space for future insertions. - */ - auto reserve(this map &self, size_type nelems) -> void + // 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. - */ + // 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); + ::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<T> + // Access a map element by key. + [[nodiscard]] auto find(this map const &self, std::string_view const key) -> std::optional<T> { - auto const *obj = ::ucl_object_lookup_len( - self.get_ucl_object(), - key.data(), key.size()); + 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 + // 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()); + 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<T> + // 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<T> { - auto *uobj = ::ucl_object_pop_keyl(self.get_ucl_object(), - key.data(), key.size()); + 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 + // 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 { - auto obj = self.find(key); - if (obj) + if (auto obj = self.find(key); obj ) return *obj; throw key_not_found(key); } diff --git a/nihil.ucl/tests/map.cc b/nihil.ucl/map.test.cc index 7240cb3..6d31af2 100644 --- a/nihil.ucl/tests/map.cc +++ b/nihil.ucl/map.test.cc @@ -1,16 +1,14 @@ -/* - * This source code is released into the public domain. - */ - -#include <concepts> +// This source code is released into the public domain. #include <catch2/catch_test_macros.hpp> #include <ucl.h> +import nihil.std; import nihil.ucl; //NOLINTBEGIN(bugprone-unchecked-optional-access) +namespace { TEST_CASE("ucl: map: invariants", "[ucl]") { using namespace nihil::ucl; @@ -43,9 +41,9 @@ TEST_CASE("ucl: map: construct from initializer_list", "[ucl]") using namespace std::literals; auto map = nihil::ucl::map<integer>{ - {"1"sv, integer(1)}, - {"42"sv, integer(42)}, - }; + {"1"sv, integer(1)}, + {"42"sv, integer(42)}, + }; REQUIRE(str(map.type()) == "object"); REQUIRE(map["1"] == 1); @@ -58,11 +56,11 @@ TEST_CASE("ucl: map: construct from range", "[ucl]") using namespace std::literals; auto vec = std::vector<std::pair<std::string_view, integer>>{ - {"1"sv, integer(1)}, - {"42"sv, integer(42)}, - }; + {"1"sv, integer(1)}, + {"42"sv, integer(42)}, + }; - auto map = nihil::ucl::map<integer>(std::from_range, vec); + auto map = nihil::ucl::map<integer>(vec); REQUIRE(str(map.type()) == "object"); REQUIRE(map["1"] == 1); @@ -75,9 +73,9 @@ TEST_CASE("ucl: map: construct from iterator pair", "[ucl]") using namespace std::literals; auto vec = std::vector<std::pair<std::string_view, integer>>{ - {"1"sv, integer(1)}, - {"42"sv, integer(42)}, - }; + {"1"sv, integer(1)}, + {"42"sv, integer(42)}, + }; auto map = nihil::ucl::map<integer>(std::ranges::begin(vec), std::ranges::end(vec)); @@ -107,9 +105,9 @@ TEST_CASE("ucl: map: find", "[ucl]") using namespace std::literals; auto map = nihil::ucl::map<integer>{ - {"1"sv, integer(1)}, - {"42"sv, integer(42)}, - }; + {"1"sv, integer(1)}, + {"42"sv, integer(42)}, + }; auto obj = map.find("42"); REQUIRE(obj.value() == 42); @@ -124,11 +122,11 @@ TEST_CASE("ucl: map: iterate", "[ucl]") using namespace std::literals; auto map = nihil::ucl::map<integer>{ - {"1"sv, integer(1)}, - {"42"sv, integer(42)}, - }; + {"1"sv, integer(1)}, + {"42"sv, integer(42)}, + }; - auto i = 0u; + auto i = 0U; for (auto [key, value] : map) { if (key == "1") @@ -155,9 +153,9 @@ TEST_CASE("ucl: map: remove", "[uc]") using namespace nihil::ucl; auto map = nihil::ucl::map<integer>{ - {"1"sv, integer(1)}, - {"42"sv, integer(42)}, - }; + {"1"sv, integer(1)}, + {"42"sv, integer(42)}, + }; REQUIRE(map.find("42") != std::nullopt); REQUIRE(map.remove("42") == true); @@ -173,9 +171,9 @@ TEST_CASE("ucl: map: pop", "[uc]") using namespace nihil::ucl; auto map = nihil::ucl::map<integer>{ - {"1"sv, integer(1)}, - {"42"sv, integer(42)}, - }; + {"1"sv, integer(1)}, + {"42"sv, integer(42)}, + }; REQUIRE(map.find("42") != std::nullopt); @@ -189,4 +187,6 @@ TEST_CASE("ucl: map: pop", "[uc]") REQUIRE(!obj); } +} // anonymous namespace + //NOLINTEND(bugprone-unchecked-optional-access) diff --git a/nihil.ucl/nihil.ucl.ccm b/nihil.ucl/nihil.ucl.ccm index b16eb3d..daa751b 100644 --- a/nihil.ucl/nihil.ucl.ccm +++ b/nihil.ucl/nihil.ucl.ccm @@ -1,13 +1,7 @@ -/* - * This source code is released into the public domain. - */ - -module; - +// This source code is released into the public domain. export module nihil.ucl; export import :emit; -export import :errc; export import :object; export import :object_cast; export import :parser; diff --git a/nihil.ucl/object.cc b/nihil.ucl/object.cc deleted file mode 100644 index 53fc4c7..0000000 --- a/nihil.ucl/object.cc +++ /dev/null @@ -1,114 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include <cstdlib> -#include <string> -#include <utility> - -#include <ucl.h> - -module nihil.ucl; - -namespace nihil::ucl { - -object::object(ref_t, ::ucl_object_t const *object) - : m_object(::ucl_object_ref(object)) -{ -} - -object::object(noref_t, ::ucl_object_t *object) - : m_object(object) -{ -} - -object::~object() { - if (m_object != nullptr) - ::ucl_object_unref(m_object); -} - -object::object(object &&other) noexcept - : m_object(std::exchange(other.m_object, nullptr)) -{} - -object::object(object const &other) - : m_object(nullptr) -{ - m_object = ::ucl_object_copy(other.get_ucl_object()); - if (m_object == nullptr) - throw std::runtime_error("failed to copy UCL object"); -} - -auto object::operator=(this object &self, object &&other) noexcept - -> object & -{ - if (&self != &other) - self.m_object = std::exchange(other.m_object, nullptr); - return self; -} - -auto object::operator=(this object &self, object const &other) -> object & -{ - return self = object(other); -} - -auto object::ref(this object const &self) -> object -{ - return object(nihil::ucl::ref, self.get_ucl_object()); -} - -auto object::type(this object const &self) -> object_type -{ - auto utype = ::ucl_object_type(self.get_ucl_object()); - return static_cast<object_type>(utype); -} - -auto object::get_ucl_object(this object &self) -> ::ucl_object_t * -{ - if (self.m_object == nullptr) - throw std::logic_error("attempt to access empty UCL object"); - return self.m_object; -} - -auto object::get_ucl_object(this object const &self) -> ::ucl_object_t const * -{ - if (self.m_object == nullptr) - throw std::logic_error("attempt to access empty UCL object"); - return self.m_object; -} - -// Return the key of this object. -auto object::key(this object const &self) -> std::string_view -{ - auto dlen = std::size_t{}; - auto const *dptr = ::ucl_object_keyl(self.get_ucl_object(), - &dlen); - return {dptr, dlen}; -} - -auto swap(object &a, object &b) -> void -{ - std::swap(a.m_object, b.m_object); -} - -auto operator<=>(object const &lhs, object const &rhs) -> std::strong_ordering -{ - auto cmp = ::ucl_object_compare(lhs.get_ucl_object(), - rhs.get_ucl_object()); - - if (cmp < 0) - return std::strong_ordering::less; - else if (cmp > 0) - return std::strong_ordering::greater; - else - return std::strong_ordering::equal; -} - -auto operator==(object const &lhs, object const &rhs) -> bool -{ - return (lhs <=> rhs) == std::strong_ordering::equal; -} - -} // namespace nihil::ucl diff --git a/nihil.ucl/object.ccm b/nihil.ucl/object.ccm index 9a7eaf7..93dc4db 100644 --- a/nihil.ucl/object.ccm +++ b/nihil.ucl/object.ccm @@ -1,88 +1,172 @@ -/* - * This source code is released into the public domain. - */ - +// This source code is released into the public domain. module; -/* - * A UCL object. The object is immutable and internally refcounted, so it - * may be copied as needed. - * - */ - -#include <compare> -#include <cstddef> -#include <string> +// A UCL object. The object is immutable and internally refcounted, so it +// may be copied as needed. #include <ucl.h> export module nihil.ucl:object; +import nihil.std; import :type; namespace nihil::ucl { -/*********************************************************************** - * The basic object type. - */ +//*********************************************************************** +// The basic object type. // Ref the UCL object when creating an object. -export inline constexpr struct ref_t {} ref; +export inline constexpr struct ref_t +{ +} ref; + // Don't ref the UCL object. -export inline constexpr struct noref_t {} noref; +export inline constexpr struct noref_t +{ +} noref; -export struct object { - inline static constexpr object_type ucl_type = object_type::object; +export struct object +{ + static constexpr object_type ucl_type = object_type::object; // Create an object from an existing ucl_object_t. The first argument // determines whether we ref the object or not. - object(ref_t, ::ucl_object_t const *object); - object(noref_t, ::ucl_object_t *object); + object(ref_t, ::ucl_object_t const *object) + : m_object(::ucl_object_ref(object)) + { + } + + object(noref_t, ::ucl_object_t *object) + : m_object(object) + { + } // Free our object on destruction. - virtual ~object(); + virtual ~object() + { + if (m_object != nullptr) + ::ucl_object_unref(m_object); + } // Movable. - object(object &&other) noexcept; - auto operator=(this object &self, object &&other) noexcept -> object&; - - // Copyable. - // Note that this copies the entire UCL object. - object(object const &other); - auto operator=(this object &self, object const &other) -> object &; + object(object &&other) noexcept + : m_object(std::exchange(other.m_object, nullptr)) + { + } + + auto operator=(this object &self, object &&other) noexcept -> object & + { + if (&self != &other) + self.m_object = std::exchange(other.m_object, nullptr); + return self; // NOLINT + } + + // Copyable. Note that this copies the entire UCL object. + + object(object const &other) + : m_object([&] { + auto *uobj = ::ucl_object_copy(other.get_ucl_object()); + if (uobj == nullptr) + throw std::runtime_error("failed to copy UCL object"); + return uobj; + }()) + { + } + + auto operator=(this object &self, object const &other) -> object & + { + if (&self != &other) + self = object(other); + return self; // NOLINT + } // Increase the refcount of this object. - [[nodiscard]] auto ref(this object const &self) -> object; + [[nodiscard]] auto ref(this object const &self) -> object + { + return {nihil::ucl::ref, self.get_ucl_object()}; + } // Return the type of this object. - [[nodiscard]] auto type(this object const &self) -> object_type; + [[nodiscard]] auto type(this object const &self) -> object_type + { + auto utype = ::ucl_object_type(self.get_ucl_object()); + return static_cast<object_type>(utype); + } // Return the underlying object. - [[nodiscard]] auto get_ucl_object(this object &self) - -> ::ucl_object_t *; - - [[nodiscard]] auto get_ucl_object(this object const &self) - -> ::ucl_object_t const *; + [[nodiscard]] auto get_ucl_object(this object &self) -> ::ucl_object_t * + { + if (self.m_object == nullptr) + throw std::logic_error("attempt to access empty UCL object"); + return self.m_object; + } + + [[nodiscard]] auto get_ucl_object(this object const &self) -> ::ucl_object_t const * + { + if (self.m_object == nullptr) + throw std::logic_error("attempt to access empty UCL object"); + return self.m_object; + } // Return the key of this object. - [[nodiscard]] auto key(this object const &self) -> std::string_view; + [[nodiscard]] auto key(this object const &self) -> std::string_view + { + auto dlen = std::size_t{}; + auto const *dptr = ::ucl_object_keyl(self.get_ucl_object(), &dlen); + return {dptr, dlen}; + } protected: + friend auto swap(object &a, object &b) noexcept -> void + { + std::swap(a.m_object, b.m_object); + } + + // Helper to validate the type of a UCL object. Throws type_mismatch if the + // type doesn't match, or else returns the pointer. + [[nodiscard]] static auto ensure_ucl_type(::ucl_object_t const *uobj, object_type type) + -> ::ucl_object_t const * + { + if (static_cast<object_type>(::ucl_object_type(uobj)) != type) + throw type_mismatch(type, static_cast<object_type>(::ucl_object_type(uobj))); + return uobj; + } + + [[nodiscard]] static auto ensure_ucl_type(::ucl_object_t *uobj, object_type type) + -> ::ucl_object_t * + { + if (static_cast<object_type>(::ucl_object_type(uobj)) != type) + throw type_mismatch(type, static_cast<object_type>(::ucl_object_type(uobj))); + return uobj; + } + +private: // The object we're wrapping. ::ucl_object_t *m_object = nullptr; - friend auto swap(object &a, object &b) -> void; + // + // Object comparison. + // + + [[nodiscard]] friend auto + operator<=>(object const &lhs, object const &rhs) -> std::strong_ordering + { + auto cmp = ::ucl_object_compare(lhs.get_ucl_object(), rhs.get_ucl_object()); + + if (cmp < 0) + return std::strong_ordering::less; + else if (cmp > 0) + return std::strong_ordering::greater; + else + return std::strong_ordering::equal; + } + + [[nodiscard]] friend auto operator==(object const &lhs, object const &rhs) -> bool + { + return (lhs <=> rhs) == std::strong_ordering::equal; + } }; -/*********************************************************************** - * Object comparison. - */ - -export [[nodiscard]] auto operator==(object const &lhs, object const &rhs) - -> bool; - -export [[nodiscard]] auto operator<=>(object const &lhs, object const &rhs) - -> std::strong_ordering; - } // namespace nihil::ucl diff --git a/nihil.ucl/tests/object.cc b/nihil.ucl/object.test.cc index 3ad180e..557653c 100644 --- a/nihil.ucl/tests/object.cc +++ b/nihil.ucl/object.test.cc @@ -1,11 +1,10 @@ -/* - * This source code is released into the public domain. - */ +// This source code is released into the public domain. #include <catch2/catch_test_macros.hpp> #include <ucl.h> +import nihil.std; import nihil.ucl; TEST_CASE("ucl object: get_ucl_object", "[ucl]") diff --git a/nihil.ucl/object_cast.ccm b/nihil.ucl/object_cast.ccm index 3fa9eba..5a09085 100644 --- a/nihil.ucl/object_cast.ccm +++ b/nihil.ucl/object_cast.ccm @@ -1,17 +1,11 @@ -/* - * This source code is released into the public domain. - */ - +// This source code is released into the public domain. module; -#include <coroutine> -#include <cstdlib> -#include <expected> - #include <ucl.h> export module nihil.ucl:object_cast; +import nihil.std; import nihil.monad; import :type; import :object; @@ -19,10 +13,9 @@ import :array; namespace nihil::ucl { -/* - * Ensure a UCL object is convertible to another type. Throws type_mismatch - * if not. - */ +// +// Ensure a UCL object is convertible to another type. +// // Implementation for basic types. template<datatype To> @@ -80,7 +73,7 @@ struct convert_check<array<T>> export template<datatype To> auto object_cast(object const &from) -> std::expected<To, type_mismatch> { - auto uobj = from.get_ucl_object(); + auto const *uobj = from.get_ucl_object(); co_await convert_check<To>{}.check(uobj); co_return To(nihil::ucl::ref, uobj); diff --git a/nihil.ucl/tests/parse.cc b/nihil.ucl/parse.test.cc index 43ce219..79a722d 100644 --- a/nihil.ucl/tests/parse.cc +++ b/nihil.ucl/parse.test.cc @@ -1,14 +1,12 @@ -/* - * This source code is released into the public domain. - */ - -#include <string> +// This source code is released into the public domain. #include <catch2/catch_test_macros.hpp> #include <catch2/matchers/catch_matchers_floating_point.hpp> +import nihil.std; import nihil.ucl; +namespace { TEST_CASE("ucl parse: iterate array", "[ucl]") { using namespace std::literals; @@ -53,3 +51,5 @@ TEST_CASE("ucl parse: iterate hash", "[ucl]") REQUIRE(object_cast<string>(value) == "test"); } } + +} // anonymous namespace diff --git a/nihil.ucl/parser.cc b/nihil.ucl/parser.cc deleted file mode 100644 index 0a08670..0000000 --- a/nihil.ucl/parser.cc +++ /dev/null @@ -1,102 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include <expected> -#include <functional> -#include <string> - -#include <ucl.h> - -module nihil.ucl; - -import nihil.error; - -namespace nihil::ucl { - -auto make_parser(int flags) -> std::expected<parser, error> -{ - auto *p = ::ucl_parser_new(flags); - if (p != nullptr) - return p; - - // TODO: Is there a way to get the actual error here? - return std::unexpected(error("failed to create parser")); -} - -auto macro_handler::handle(unsigned char const *data, - std::size_t len, void *ud) - -> bool -{ - auto handler = static_cast<macro_handler *>(ud); - auto string = std::string_view( - reinterpret_cast<char const *>(data), - len); - return handler->callback(string); -} - -parser::parser(::ucl_parser *uclp) - : m_parser(uclp) -{ -} - -parser::~parser() -{ - if (m_parser) - ::ucl_parser_free(m_parser); -} - -parser::parser(parser &&other) noexcept - : m_parser(std::exchange(other.m_parser, nullptr)) - , m_macros(std::move(other.m_macros)) -{ -} - -auto parser::operator=(this parser &self, parser &&other) noexcept - -> parser & -{ - if (&self != &other) { - if (self.m_parser) - ::ucl_parser_free(self.m_parser); - - self.m_parser = std::exchange(other.m_parser, nullptr); - self.m_macros = std::move(other.m_macros); - } - - return self; -} - -auto parser::register_value( - this parser &self, - std::string_view variable, - std::string_view value) - -> void -{ - ::ucl_parser_register_variable( - self.get_parser(), - std::string(variable).c_str(), - std::string(value).c_str()); -} - -auto parser::top(this parser &self) -> map<object> -{ - auto obj = ::ucl_parser_get_object(self.get_parser()); - if (obj != nullptr) - // ucl_parser_get_object() refs the object for us. - return {noref, obj}; - - throw std::logic_error( - "attempt to call top() on an invalid ucl::parser"); -} - -auto parser::get_parser(this parser &self) -> ::ucl_parser * -{ - if (self.m_parser == nullptr) - throw std::logic_error("attempt to fetch a null ucl::parser"); - - return self.m_parser; -} - -} // namespace nihil::ucl diff --git a/nihil.ucl/parser.ccm b/nihil.ucl/parser.ccm index 5fa3495..0100fda 100644 --- a/nihil.ucl/parser.ccm +++ b/nihil.ucl/parser.ccm @@ -1,21 +1,11 @@ -/* - * This source code is released into the public domain. - */ - +// This source code is released into the public domain. module; -#include <coroutine> -#include <expected> -#include <format> -#include <functional> -#include <memory> -#include <string> -#include <vector> - #include <ucl.h> export module nihil.ucl:parser; +import nihil.std; import nihil.monad; import :object; import :map; @@ -28,79 +18,98 @@ export inline constexpr int parser_zerocopy = UCL_PARSER_ZEROCOPY; export inline constexpr int parser_no_time = UCL_PARSER_NO_TIME; // A macro handler. This proxies the C API callback to the C++ API. -using macro_callback_t = bool (std::string_view); +using macro_callback_t = bool(std::string_view); -struct macro_handler { +struct macro_handler +{ std::function<macro_callback_t> callback; // Handle a callback from the C API. - static auto handle( - unsigned char const *data, - std::size_t len, void - *ud) - -> bool; + static auto handle(unsigned char const *data, std::size_t len, void *ud) -> bool + { + auto handler = static_cast<macro_handler *>(ud); + auto string = std::string_view(reinterpret_cast<char const *>(data), len); + return handler->callback(string); + } }; -/* - * A UCL parser. This wraps the C ucl_parser API. - * - * parser itself is not exported; use make_parser() to create one. - */ -struct parser { +// A UCL parser. This wraps the C ucl_parser API. +// +// parser itself is not exported; use make_parser() to create one. +struct parser +{ // Create a parser from a UCL parser. - parser(::ucl_parser *); + explicit parser(::ucl_parser *uclp) + : m_parser(uclp) + { + } // Destroy our parser when we're destroyed. - ~parser(); + ~parser() + { + if (m_parser != nullptr) + ::ucl_parser_free(m_parser); + } // Not copyable. parser(parser const &) = delete; auto operator=(this parser &, parser const &) -> parser & = delete; // Movable. - parser(parser &&) noexcept; - auto operator=(this parser &, parser &&) noexcept -> parser &; + parser(parser &&other) noexcept + : m_parser(std::exchange(other.m_parser, nullptr)) + , m_macros(std::move(other.m_macros)) + { + } + + auto operator=(this parser &self, parser &&other) noexcept -> parser & + { + if (&self != &other) { + if (self.m_parser != nullptr) + ::ucl_parser_free(self.m_parser); + + self.m_parser = std::exchange(other.m_parser, nullptr); + self.m_macros = std::move(other.m_macros); + } + + return self; // NOLINT + } // Add a parser macro. Unlike ucl_parser_register_macro, this doesn't // take a userdata parameter; it's assumed the user will use lambda // capture or similar if needed. - template<std::invocable<std::string_view> F> - auto register_macro(this parser &self, - std::string_view name, - F &&func) - -> void - requires (std::same_as<bool, std::invoke_result<F>>) + + template <std::invocable<std::string_view> F> + auto register_macro(this parser &self, std::string_view name, F &&func) -> void + requires(std::same_as<bool, std::invoke_result<F>>) { - auto handler = std::make_unique<macro_handler>( - std::forward<F>(func)); + auto handler = std::make_unique<macro_handler>(std::forward<F>(func)); auto cname = std::string(name); - ::ucl_parser_register_macro( - self.get_parser(), cname.c_str(), - ¯o_handler::handle, handler.get()); + ::ucl_parser_register_macro(self.get_parser(), cname.c_str(), + ¯o_handler::handle, handler.get()); self.m_macros.emplace_back(std::move(handler)); } // Add a parser variable. - auto register_value(this parser &self, - std::string_view variable, - std::string_view value) - -> void; + auto + register_value(this parser &self, std::string_view variable, std::string_view value) -> void + { + ::ucl_parser_register_variable(self.get_parser(), std::string(variable).c_str(), + std::string(value).c_str()); + } // Add data to the parser. - [[nodiscard]] auto add(this parser &self, - std::ranges::contiguous_range auto &&data) + [[nodiscard]] auto add(this parser &self, std::ranges::contiguous_range auto &&data) -> std::expected<void, error> - // Only bytes (chars) are permitted. + // Only bytes (chars) are permitted. requires(sizeof(std::ranges::range_value_t<decltype(data)>) == 1) { auto *p = self.get_parser(); - auto dptr = reinterpret_cast<unsigned char const *>( - std::ranges::data(data)); + auto dptr = reinterpret_cast<unsigned char const *>(std::ranges::data(data)); - auto ret = ::ucl_parser_add_chunk( - p, dptr, std::ranges::size(data)); + auto ret = ::ucl_parser_add_chunk(p, dptr, std::ranges::size(data)); if (ret == true) return {}; @@ -108,23 +117,34 @@ struct parser { return std::unexpected(error(::ucl_parser_get_error(p))); } - [[nodiscard]] auto add(this parser &self, - std::ranges::range auto &&data) - -> std::expected<void, error> - requires (!std::ranges::contiguous_range<decltype(data)>) + [[nodiscard]] auto + add(this parser &self, std::ranges::range auto &&data) -> std::expected<void, error> + requires(!std::ranges::contiguous_range<decltype(data)>) { - auto cdata = std::vector<char>( - std::from_range, - std::forward<decltype(data)>(data)); + auto cdata = std::vector<char>(std::from_range, std::forward<decltype(data)>(data)); co_await self.add(std::move(cdata)); co_return {}; } // Return the top object of this parser. - [[nodiscard]] auto top(this parser &self) -> map<object>; + [[nodiscard]] auto top(this parser &self) -> map<object> + { + auto *obj = ::ucl_parser_get_object(self.get_parser()); + if (obj != nullptr) + // ucl_parser_get_object() refs the object for us. + return {noref, obj}; + + throw std::logic_error("attempt to call top() on an invalid ucl::parser"); + } // Return the stored parser object. - [[nodiscard]] auto get_parser(this parser &self) -> ::ucl_parser *; + [[nodiscard]] auto get_parser(this parser &self) -> ::ucl_parser * + { + if (self.m_parser == nullptr) + throw std::logic_error("attempt to fetch a null ucl::parser"); + + return self.m_parser; + } private: // The parser object. Should never be null, unless we've been @@ -137,22 +157,26 @@ private: }; // Create a parser with the given flags. -export [[nodiscard]] auto -make_parser(int flags = 0) -> std::expected<parser, error>; +export [[nodiscard]] auto make_parser(int flags = 0) -> std::expected<parser, error> +{ + auto *p = ::ucl_parser_new(flags); + if (p != nullptr) + return {parser(p)}; + + // TODO: Is there a way to get the actual error here? + return std::unexpected(error("failed to create parser")); +} // Utility function to parse something and return the top-level object. export [[nodiscard]] auto -parse(int flags, std::ranges::range auto &&data) - -> std::expected<map<object>, error> +parse(int flags, std::ranges::range auto &&data) -> std::expected<map<object>, error> { auto p = co_await make_parser(flags); co_await p.add(std::forward<decltype(data)>(data)); co_return p.top(); } -export [[nodiscard]] auto -parse(std::ranges::range auto &&data) - -> std::expected<map<object>, error> +export [[nodiscard]] auto parse(std::ranges::range auto &&data) -> std::expected<map<object>, error> { co_return co_await parse(0, std::forward<decltype(data)>(data)); } diff --git a/nihil.ucl/real.cc b/nihil.ucl/real.cc deleted file mode 100644 index 6d9e082..0000000 --- a/nihil.ucl/real.cc +++ /dev/null @@ -1,104 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include <cassert> -#include <compare> -#include <cstdlib> -#include <expected> -#include <string> -#include <system_error> - -#include <ucl.h> - -module nihil.ucl; - -import nihil.error; - -namespace nihil::ucl { - -auto make_real(real::contained_type value) - -> std::expected<real, error> -{ - auto *uobj = ::ucl_object_fromdouble(value); - if (uobj == nullptr) - return std::unexpected(error( - errc::failed_to_create_object, - error(std::errc(errno)))); - - return real(noref, uobj); -} - -real::real() - : real(0) -{ -} - -real::real(contained_type value) - : real(noref, [&] { - auto *uobj = ::ucl_object_fromdouble(value); - if (uobj == nullptr) - throw std::system_error( - std::make_error_code(std::errc(errno))); - return uobj; - }()) -{ -} - -real::real(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 != real::ucl_type) - throw type_mismatch(real::ucl_type, actual_type); - return uobj; - }()) -{ -} - -real::real(noref_t, ::ucl_object_t *uobj) - : object(nihil::ucl::noref, [&] { - auto actual_type = static_cast<object_type>( - ::ucl_object_type(uobj)); - if (actual_type != real::ucl_type) - throw type_mismatch(real::ucl_type, actual_type); - return uobj; - }()) -{ -} - -auto real::value(this real const &self) -> contained_type -{ - auto v = contained_type{}; - auto const *uobj = self.get_ucl_object(); - - if (::ucl_object_todouble_safe(uobj, &v)) - return v; - - std::abort(); -} - -auto operator== (real const &a, real const &b) -> bool -{ - return a.value() == b.value(); -} - -auto operator<=> (real const &a, real const &b) -> std::partial_ordering -{ - return a.value() <=> b.value(); -} - -auto operator== (real const &a, real::contained_type b) -> bool -{ - return a.value() == b; -} - -auto operator<=> (real const &a, real::contained_type b) - -> std::partial_ordering -{ - return a.value() <=> b; -} - -} // namespace nihil::ucl diff --git a/nihil.ucl/real.ccm b/nihil.ucl/real.ccm index f425a9a..b432617 100644 --- a/nihil.ucl/real.ccm +++ b/nihil.ucl/real.ccm @@ -1,78 +1,106 @@ -/* - * This source code is released into the public domain. - */ - +// This source code is released into the public domain. module; -#include <compare> -#include <expected> -#include <format> -#include <utility> - #include <ucl.h> export module nihil.ucl:real; +import nihil.std; +import nihil.core; import :object; import :type; namespace nihil::ucl { -export struct real final : object { +export struct real final : object +{ using contained_type = double; - inline static constexpr object_type ucl_type = object_type::real; + static constexpr object_type ucl_type = object_type::real; - /* - * Create a real holding the value 0. Throws std::system_error - * on failure. - */ - real(); + // Create a real holding the value 0. Throws std::system_error + // on failure. + real() + : real(0) + { + } - /* - * Create a real holding a specific value. Throws std::system_error - * on failure. - */ - explicit real(contained_type value); + // Create a real holding a specific value. Throws std::system_error + // on failure. + explicit real(contained_type value) + : real(noref, [&] { + auto *uobj = ::ucl_object_fromdouble(value); + if (uobj == nullptr) + throw std::system_error(std::make_error_code(sys_error())); + return uobj; + }()) + { + } - /* - * Create a new real from a UCL object. Throws type_mismatch - * on failure. - */ - real(ref_t, ::ucl_object_t const *uobj); - real(noref_t, ::ucl_object_t *uobj); + // Create a new real from a UCL object. Throws type_mismatch + // on failure. + real(ref_t, ::ucl_object_t const *uobj) + : object(nihil::ucl::ref, ensure_ucl_type(uobj, real::ucl_type)) + { + } + + real(noref_t, ::ucl_object_t *uobj) + : object(nihil::ucl::noref, ensure_ucl_type(uobj, real::ucl_type)) + { + } // Return the value of this real. - [[nodiscard]] auto value(this real const &self) -> contained_type; -}; + [[nodiscard]] auto value(this real const &self) -> contained_type + { + auto v = contained_type{}; + auto const *uobj = self.get_ucl_object(); + + if (::ucl_object_todouble_safe(uobj, &v)) + return v; -/* - * Real constructors. These return an error instead of throwing. - */ + throw std::runtime_error("ucl_object_todouble_safe failed"); + } -export [[nodiscard]] auto -make_real(real::contained_type = 0) -> std::expected<real, error>; +private: + // + // Comparison operators. + // -/* - * Comparison operators. - */ + [[nodiscard]] friend auto operator==(real const &a, real const &b) -> bool + { + return a.value() == b.value(); + } -export [[nodiscard]] auto operator== (real const &a, real const &b) -> bool; + [[nodiscard]] friend auto operator<=>(real const &a, real const &b) -> std::partial_ordering + { + return a.value() <=> b.value(); + } -export [[nodiscard]] auto operator== (real const &a, - real::contained_type b) -> bool; + [[nodiscard]] friend auto operator==(real const &a, real::contained_type b) -> bool + { + return a.value() == b; + } -export [[nodiscard]] auto operator<=> (real const &a, real const &b) - -> std::partial_ordering; + [[nodiscard]] friend auto + operator<=>(real const &a, real::contained_type b) -> std::partial_ordering + { + return a.value() <=> b; + } +}; -export [[nodiscard]] auto operator<=> (real const &a, real::contained_type b) - -> std::partial_ordering; +// Real constructor. This returns an error instead of throwing. +export [[nodiscard]] auto make_real(real::contained_type value = 0) -> std::expected<real, error> +{ + auto *uobj = ::ucl_object_fromdouble(value); + if (uobj == nullptr) + return std::unexpected(error(errc::failed_to_create_object, error(sys_error()))); -/* - * Literal operator. - */ + return real(noref, uobj); +} + +// Literal operator. inline namespace literals { -export constexpr auto operator""_ucl (long double d) -> real +export constexpr auto operator""_ucl(long double d) -> real { if (d > static_cast<long double>(std::numeric_limits<double>::max()) || d < static_cast<long double>(std::numeric_limits<double>::min())) @@ -80,32 +108,31 @@ export constexpr auto operator""_ucl (long double d) -> real return real(static_cast<double>(d)); } -} // namespace nihil::ucl::literals +} // namespace literals } // namespace nihil::ucl -namespace nihil { inline namespace literals { - export using namespace ::nihil::ucl::literals; -}} // namespace nihil::literals +namespace nihil { +inline namespace literals { +export using namespace ::nihil::ucl::literals; +} +} // namespace nihil -/* - * std::formatter for a real. This provides the same format operations - * as std::formatter<double>; - */ -export template<> +// std::formatter for a real. This provides the same format operations +// as std::formatter<double>; +export template <> struct std::formatter<nihil::ucl::real, char> { std::formatter<double> base_formatter; - template<class ParseContext> - constexpr ParseContext::iterator parse(ParseContext& ctx) + template <class ParseContext> + constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator { return base_formatter.parse(ctx); } - template<class FmtContext> - FmtContext::iterator format(nihil::ucl::real const &o, - FmtContext& ctx) const + template <class FmtContext> + auto format(nihil::ucl::real const &o, FmtContext &ctx) const -> FmtContext::iterator { return base_formatter.format(o.value(), ctx); } diff --git a/nihil.ucl/tests/real.cc b/nihil.ucl/real.test.cc index 421917e..e880d9a 100644 --- a/nihil.ucl/tests/real.cc +++ b/nihil.ucl/real.test.cc @@ -1,16 +1,13 @@ -/* - * This source code is released into the public domain. - */ - -#include <concepts> -#include <string> +// This source code is released into the public domain. #include <catch2/catch_test_macros.hpp> #include <catch2/matchers/catch_matchers_floating_point.hpp> #include <ucl.h> +import nihil.std; import nihil.ucl; +namespace { TEST_CASE("ucl: real: invariants", "[ucl]") { using namespace nihil::ucl; @@ -32,12 +29,12 @@ TEST_CASE("ucl: real: constructor", "[ucl]") { using namespace nihil::ucl; - SECTION("default") { + SECTION ("default") { auto r = real(); REQUIRE(r == 0); } - SECTION("with value") { + SECTION ("with value") { auto r = real(42.1); REQUIRE_THAT(r.value(), Catch::Matchers::WithinRel(42.1)); } @@ -45,7 +42,7 @@ TEST_CASE("ucl: real: constructor", "[ucl]") TEST_CASE("ucl: real: literal", "[ucl]") { - SECTION("with namespace nihil::ucl::literals") { + SECTION ("with namespace nihil::ucl::literals") { using namespace nihil::ucl::literals; auto r = 42.5_ucl; @@ -53,7 +50,7 @@ TEST_CASE("ucl: real: literal", "[ucl]") REQUIRE_THAT(r.value(), Catch::Matchers::WithinRel(42.5)); } - SECTION("with namespace nihil::literals") { + SECTION ("with namespace nihil::literals") { using namespace nihil::literals; auto r = 42.5_ucl; @@ -66,8 +63,8 @@ TEST_CASE("ucl: real: construct from UCL object", "[ucl]") { using namespace nihil::ucl; - SECTION("ref, correct type") { - auto uobj = ::ucl_object_fromdouble(42); + SECTION ("ref, correct type") { + auto *uobj = ::ucl_object_fromdouble(42); auto r = real(ref, uobj); REQUIRE(r == 42); @@ -75,23 +72,23 @@ TEST_CASE("ucl: real: construct from UCL object", "[ucl]") ::ucl_object_unref(uobj); } - SECTION("noref, correct type") { - auto uobj = ::ucl_object_fromdouble(42); + SECTION ("noref, correct type") { + auto *uobj = ::ucl_object_fromdouble(42); auto r = real(noref, uobj); REQUIRE(r == 42); } - SECTION("ref, wrong type") { - auto uobj = ::ucl_object_fromint(42); + SECTION ("ref, wrong type") { + auto *uobj = ::ucl_object_fromint(42); REQUIRE_THROWS_AS(real(ref, uobj), type_mismatch); ::ucl_object_unref(uobj); } - SECTION("noref, wrong type") { - auto uobj = ::ucl_object_fromint(42); + SECTION ("noref, wrong type") { + auto *uobj = ::ucl_object_fromint(42); REQUIRE_THROWS_AS(real(noref, uobj), type_mismatch); @@ -103,12 +100,12 @@ TEST_CASE("ucl: real: make_real", "[ucl]") { using namespace nihil::ucl; - SECTION("default value") { + SECTION ("default value") { auto i = make_real().value(); REQUIRE(i == 0); } - SECTION("explicit value") { + SECTION ("explicit value") { auto i = make_real(42).value(); REQUIRE(i == 42); } @@ -117,7 +114,7 @@ TEST_CASE("ucl: real: make_real", "[ucl]") TEST_CASE("ucl: real: swap", "[ucl]") { // do not add using namespace nihil::ucl - + auto r1 = nihil::ucl::real(1); auto r2 = nihil::ucl::real(2); @@ -139,15 +136,15 @@ TEST_CASE("ucl: real: key()", "[ucl]") { using namespace nihil::ucl; - SECTION("parsed with key") { + SECTION ("parsed with key") { auto obj = parse("a_real = 42.5").value(); auto r = object_cast<real>(obj["a_real"]).value(); REQUIRE(r.key() == "a_real"); } - SECTION("bare real, no key") { + SECTION ("bare real, no key") { auto i = 42.5_ucl; - REQUIRE(i.key() == ""); + REQUIRE(i.key().empty() == true); } } @@ -157,22 +154,22 @@ TEST_CASE("ucl: real: comparison", "[ucl]") auto i = nihil::ucl::real(42.5); - SECTION("operator==") { + SECTION ("operator==") { REQUIRE(i == 42.5); REQUIRE(i == 42.5_ucl); } - SECTION("operator!=") { + SECTION ("operator!=") { REQUIRE(i != 1); REQUIRE(i != 1._ucl); } - SECTION("operator<") { + SECTION ("operator<") { REQUIRE(i < 43); REQUIRE(i < 43._ucl); } - SECTION("operator>") { + SECTION ("operator>") { REQUIRE(i > 1); REQUIRE(i > 1._ucl); } @@ -186,8 +183,7 @@ TEST_CASE("ucl: real: parse", "[ucl]") auto v = obj["value"]; REQUIRE(v.key() == "value"); - REQUIRE_THAT(object_cast<real>(v).value().value(), - Catch::Matchers::WithinRel(42.1)); + REQUIRE_THAT(object_cast<real>(v).value().value(), Catch::Matchers::WithinRel(42.1)); } TEST_CASE("ucl: real: parse and emit", "[ucl]") @@ -206,12 +202,12 @@ TEST_CASE("ucl: real: format", "[ucl]") { using namespace nihil::ucl; - SECTION("bare real") { + SECTION ("bare real") { auto str = std::format("{}", 42.5_ucl); REQUIRE(str == "42.5"); } - SECTION("parsed real") { + SECTION ("parsed real") { auto obj = parse("real = 42.5;").value(); auto r = object_cast<real>(obj["real"]).value(); @@ -219,7 +215,7 @@ TEST_CASE("ucl: real: format", "[ucl]") REQUIRE(str == "42.5"); } - SECTION("with format string") { + SECTION ("with format string") { auto str = std::format("{:10.5f}", 42.5_ucl); REQUIRE(str == " 42.50000"); } @@ -229,14 +225,14 @@ TEST_CASE("ucl: real: print to ostream", "[ucl]") { using namespace nihil::ucl; - SECTION("bare real") { + SECTION ("bare real") { auto strm = std::ostringstream(); strm << 42.5_ucl; REQUIRE(strm.str() == "42.5"); } - SECTION("parsed real") { + SECTION ("parsed real") { auto obj = parse("real = 42.5;").value(); auto i = object_cast<real>(obj["real"]).value(); @@ -246,3 +242,4 @@ TEST_CASE("ucl: real: print to ostream", "[ucl]") REQUIRE(strm.str() == "42.5"); } } +} // anonymous namespace diff --git a/nihil.ucl/string.cc b/nihil.ucl/string.cc deleted file mode 100644 index 67e97f4..0000000 --- a/nihil.ucl/string.cc +++ /dev/null @@ -1,187 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include <cstdlib> -#include <expected> -#include <iosfwd> -#include <string> -#include <system_error> - -#include <ucl.h> - -module nihil.ucl; - -import nihil.error; - -namespace nihil::ucl { - -auto make_string() -> std::expected<string, error> -{ - return make_string(std::string_view("")); -} - -auto make_string(char const *s) -> std::expected<string, error> -{ - return make_string(std::string_view(s)); -} - -auto make_string(std::string_view s) -> std::expected<string, error> -{ - auto *uobj = ::ucl_object_fromstring_common( - s.data(), s.size(), UCL_STRING_RAW); - - if (uobj == nullptr) - return std::unexpected(error( - errc::failed_to_create_object, - error(std::errc(errno)))); - - return string(noref, uobj); -} - -string::string(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 != string::ucl_type) - throw type_mismatch(string::ucl_type, actual_type); - return uobj; - }()) -{ -} - -string::string(noref_t, ::ucl_object_t *uobj) - : object(nihil::ucl::noref, [&] { - auto actual_type = static_cast<object_type>( - ::ucl_object_type(uobj)); - if (actual_type != string::ucl_type) - throw type_mismatch(string::ucl_type, actual_type); - return uobj; - }()) -{ -} - -string::string() - : string(std::string_view("")) -{} - -string::string(std::string_view value) - : string(noref, [&] { - auto *uobj = ::ucl_object_fromstring_common( - value.data(), value.size(), UCL_STRING_RAW); - if (uobj == nullptr) - throw std::system_error( - std::make_error_code(std::errc(errno))); - return uobj; - }()) -{ -} - -string::string(char const *value) - : string(std::string_view(value)) -{ -} - -auto string::value(this string const &self) -> contained_type -{ - char const *dptr{}; - std::size_t dlen; - - auto const *uobj = self.get_ucl_object(); - if (::ucl_object_tolstring_safe(uobj, &dptr, &dlen)) - return {dptr, dlen}; - - // This should never fail. - std::abort(); -} - -auto string::size(this string const &self) -> size_type -{ - return self.value().size(); -} - -auto string::empty(this string const &self) -> bool -{ - return self.size() == 0; -} - -auto string::data(this string const &self) -> pointer -{ - char const *dptr{}; - - auto const *uobj = self.get_ucl_object(); - if (::ucl_object_tostring_safe(uobj, &dptr)) - return dptr; - - // This should never fail. - std::abort(); -} - -auto string::begin(this string const &self) -> iterator -{ - return self.data(); -} - -auto string::end(this string const &self) -> iterator -{ - return self.data() + self.size(); -} - -auto operator== (string const &a, string const &b) - -> bool -{ - return a.value() == b.value(); -} - -auto operator<=> (string const &a, string const &b) - -> std::strong_ordering -{ - return a.value() <=> b.value(); -} - -/* - * For convenience, allow comparison with C++ strings without having to - * construct a temporary UCL object. - */ - -auto operator==(string const &lhs, std::string_view rhs) -> bool -{ - return lhs.value() == rhs; -} - -auto operator<=>(string const &lhs, std::string_view rhs) - -> std::strong_ordering -{ - return lhs.value() <=> rhs; -} - -auto operator==(string const &lhs, std::string const &rhs) -> bool -{ - return lhs == std::string_view(rhs); -} - -auto operator<=>(string const &lhs, std::string const &rhs) - -> std::strong_ordering -{ - return lhs <=> std::string_view(rhs); -} - -auto operator==(string const &lhs, char const *rhs) -> bool -{ - return lhs == std::string_view(rhs); -} - -auto operator<=>(string const &lhs, char const *rhs) - -> std::strong_ordering -{ - return lhs <=> std::string_view(rhs); -} - -auto operator<<(std::ostream &strm, string const &s) -> std::ostream & -{ - return strm << s.value(); -} - -} // namespace nihil::ucl diff --git a/nihil.ucl/string.ccm b/nihil.ucl/string.ccm index c757bf1..4b46e39 100644 --- a/nihil.ucl/string.ccm +++ b/nihil.ucl/string.ccm @@ -1,27 +1,21 @@ -/* - * This source code is released into the public domain. - */ - +// This source code is released into the public domain. module; -#include <cstdlib> -#include <expected> -#include <format> -#include <iosfwd> -#include <string> - #include <ucl.h> export module nihil.ucl:string; +import nihil.std; +import nihil.core; import :object; import :type; namespace nihil::ucl { -export struct string final : object { +export struct string final : object +{ using contained_type = std::string_view; - inline static constexpr object_type ucl_type = object_type::string; + static constexpr object_type ucl_type = object_type::string; // string is a container of char using value_type = char const; @@ -31,159 +25,236 @@ export struct string final : object { using pointer = value_type *; using iterator = pointer; - /* - * Create a new empty string. Throws std::system_error on failure. - */ - string(); - - /* - * Create a string from a value. Throws std::system_error on failure. - */ - explicit string(std::string_view); - - /* - * Create a string from a C literal. Throws std::system_error - * on failure. - */ - explicit string(char const *); - - /* - * Create a string from a contiguous range. The range's value type - * must be char. Throws std::system_error on failure. - */ - template<std::ranges::contiguous_range Range> - requires (!std::same_as<std::string_view, Range> && - std::same_as<char, std::ranges::range_value_t<Range>>) - explicit string(Range &&range) - : string(std::string_view(std::ranges::begin(range), - std::ranges::end(range))) - {} - - /* - * Create a string from a non-contiguous range. This requires a - * temporary value due to limitations of the UCL C API. - */ - template<std::ranges::range Range> - requires (!std::ranges::contiguous_range<Range> && - std::same_as<char, std::ranges::range_value_t<Range>>) - explicit string(Range &&range) - : string(std::string(std::from_range, range)) - {} - - /* - * Create a string from an iterator pair. The iterator's value type - * must be char. If the iterator pair is not contiguous, the value - * will be copied to a temporary first. - * - * Throws std::system_error on failure. - */ - template<std::input_iterator Iterator> - requires (std::same_as<char, std::iter_value_t<Iterator>>) + // Create a new empty string. Throws std::system_error on failure. + string() + : string(std::string_view("")) + { + } + + // Create a string from a value. Throws std::system_error on failure. + explicit string(std::string_view value) + : string(noref, [&] { + auto *uobj = ::ucl_object_fromstring_common(value.data(), value.size(), + UCL_STRING_RAW); + if (uobj == nullptr) + throw std::system_error(std::make_error_code(sys_error())); + return uobj; + }()) + { + } + + // Create a string from a C literal. Throws std::system_error + // on failure. + explicit string(char const *value) + : string(std::string_view(value)) + { + } + + // Create a string from a contiguous range. The range's value type + // must be char. Throws std::system_error on failure. + template <std::ranges::contiguous_range Range> + requires(!std::same_as<std::string_view, Range> && + std::same_as<char, std::ranges::range_value_t<Range>>) + explicit string(Range const &range) + : string(std::string_view(std::ranges::begin(range), std::ranges::end(range))) + { + } + + // Create a string from a non-contiguous range. This requires a + // temporary value due to limitations of the UCL C API. + template <std::ranges::range Range> + requires(!std::ranges::contiguous_range<Range> && + std::same_as<char, std::ranges::range_value_t<Range>>) + explicit string(Range range) + : string(std::string(std::from_range, std::forward<Range>(range))) + { + } + + // Create a string from an iterator pair. The iterator's value type + // must be char. If the iterator pair is not contiguous, the value + // will be copied to a temporary first. + // + // Throws std::system_error on failure. + template <std::input_iterator Iterator> + requires(std::same_as<char, std::iter_value_t<Iterator>>) string(Iterator first, Iterator last) : string(std::ranges::subrange(first, last)) - {} + { + } + + // Create a new string from a UCL object. Throws type_mismatch + // on failure. + string(ref_t, ::ucl_object_t const *uobj) + : object(nihil::ucl::ref, ensure_ucl_type(uobj, string::ucl_type)) + { + } - /* - * Create a new string from a UCL object. Throws type_mismatch - * on failure. - */ - string(ref_t, ::ucl_object_t const *uobj); - string(noref_t, ::ucl_object_t *uobj); + string(noref_t, ::ucl_object_t *uobj) + : object(nihil::ucl::noref, ensure_ucl_type(uobj, string::ucl_type)) + { + } // Return the value of this string. - [[nodiscard]] auto value(this string const &self) -> contained_type; + [[nodiscard]] auto value(this string const &self) -> contained_type + { + char const *dptr{}; + std::size_t dlen{}; + + auto const *uobj = self.get_ucl_object(); + if (::ucl_object_tolstring_safe(uobj, &dptr, &dlen)) + return {dptr, dlen}; + + throw std::runtime_error("ucl_object_tolstring_safe() failed"); + } // Return the size of this string. - [[nodiscard]] auto size(this string const &self) -> size_type; + [[nodiscard]] auto size(this string const &self) -> size_type + { + return self.value().size(); + } // Test if this string is empty. - [[nodiscard]] auto empty(this string const &self) -> bool; + [[nodiscard]] auto empty(this string const &self) -> bool + { + return self.size() == 0; + } // Access this string's data - [[nodiscard]] auto data(this string const &self) -> pointer; + [[nodiscard]] auto data(this string const &self) -> pointer + { + char const *dptr{}; + + auto const *uobj = self.get_ucl_object(); + if (::ucl_object_tostring_safe(uobj, &dptr)) + return dptr; + + throw std::runtime_error("ucl_object_tostring_safe() failed"); + } // Iterator access - [[nodiscard]] auto begin(this string const &self) -> iterator; - [[nodiscard]] auto end(this string const &self) -> iterator; -}; + [[nodiscard]] auto begin(this string const &self) -> iterator + { + return self.data(); + } -/* - * String constructors. These return an error instead of throwing. - */ + [[nodiscard]] auto end(this string const &self) -> iterator + { + return self.data() + self.size(); + } -// Empty string -export [[nodiscard]] auto -make_string() -> std::expected<string, error>; +private: + // + // Comparison operators. + // + + [[nodiscard]] friend auto operator==(string const &a, string const &b) -> bool + { + return a.value() == b.value(); + } + + [[nodiscard]] friend auto + operator<=>(string const &a, string const &b) -> std::strong_ordering + { + return a.value() <=> b.value(); + } + + // For convenience, allow comparison with C++ strings without having to + // construct a temporary UCL object. + + [[nodiscard]] friend auto operator==(string const &lhs, std::string_view rhs) -> bool + { + return lhs.value() == rhs; + } + + [[nodiscard]] friend auto + operator<=>(string const &lhs, std::string_view rhs) -> std::strong_ordering + { + return lhs.value() <=> rhs; + } + + [[nodiscard]] friend auto operator==(string const &lhs, std::string const &rhs) -> bool + { + return lhs == std::string_view(rhs); + } + + [[nodiscard]] friend auto + operator<=>(string const &lhs, std::string const &rhs) -> std::strong_ordering + { + return lhs <=> std::string_view(rhs); + } + + [[nodiscard]] friend auto operator==(string const &lhs, char const *rhs) -> bool + { + return lhs == std::string_view(rhs); + } + + [[nodiscard]] friend auto + operator<=>(string const &lhs, char const *rhs) -> std::strong_ordering + { + return lhs <=> std::string_view(rhs); + } + + // Stream output. + friend auto operator<<(std::ostream &strm, string const &s) -> std::ostream & + { + return strm << s.value(); + } +}; + +// +// String constructors. These return an error instead of throwing. +// // From string_view -export [[nodiscard]] auto -make_string(std::string_view) -> std::expected<string, error>; +export [[nodiscard]] auto make_string(std::string_view s) -> std::expected<string, error> +{ + auto *uobj = ::ucl_object_fromstring_common(s.data(), s.size(), UCL_STRING_RAW); + + if (uobj == nullptr) + return std::unexpected(error(errc::failed_to_create_object, error(sys_error()))); + + return string(noref, uobj); +} + +// Empty string +export [[nodiscard]] auto make_string() -> std::expected<string, error> +{ + return make_string(std::string_view("")); +} // From C literal -export [[nodiscard]] auto -make_string(char const *) -> std::expected<string, error>; +export [[nodiscard]] auto make_string(char const *s) -> std::expected<string, error> +{ + return make_string(std::string_view(s)); +} // From contiguous range -export template<std::ranges::contiguous_range Range> -requires (!std::same_as<std::string_view, Range> && - std::same_as<char, std::ranges::range_value_t<Range>>) +export template <std::ranges::contiguous_range Range> +requires(!std::same_as<std::string_view, Range> && + std::same_as<char, std::ranges::range_value_t<Range>>) [[nodiscard]] auto make_string(Range &&range) { - return make_string(std::string_view(range)); + return make_string(std::string_view(std::forward<Range>(range))); } // From non-contiguous range -export template<std::ranges::range Range> -requires (!std::ranges::contiguous_range<Range> && - std::same_as<char, std::ranges::range_value_t<Range>>) +export template <std::ranges::range Range> +requires(!std::ranges::contiguous_range<Range> && + std::same_as<char, std::ranges::range_value_t<Range>>) [[nodiscard]] auto make_string(Range &&range) { - return make_string(std::string(std::from_range, range)); + return make_string(std::string(std::from_range, std::forward<Range>(range))); } // From iterator pair -export template<std::input_iterator Iterator> -requires (std::same_as<char, std::iter_value_t<Iterator>>) +export template <std::input_iterator Iterator> +requires(std::same_as<char, std::iter_value_t<Iterator>>) [[nodiscard]] auto make_string(Iterator first, Iterator last) { return make_string(std::ranges::subrange(first, last)); } /* - * Comparison operators. - */ - -export [[nodiscard]] auto operator== (string const &a, string const &b) -> bool; -export [[nodiscard]] auto operator<=> (string const &a, string const &b) - -> std::strong_ordering; - -/* - * For convenience, allow comparison with C++ strings without having to - * construct a temporary UCL object. - */ - -export [[nodiscard]] auto operator==(string const &lhs, - std::string_view rhs) -> bool; - -export [[nodiscard]] auto operator==(string const &lhs, - std::string const &rhs) -> bool; - -export [[nodiscard]] auto operator==(string const &lhs, - char const *rhs) -> bool; - -export [[nodiscard]] auto operator<=>(string const &lhs, - std::string_view rhs) - -> std::strong_ordering; - -export [[nodiscard]] auto operator<=>(string const &lhs, - std::string const &rhs) - -> std::strong_ordering; - -export [[nodiscard]] auto operator<=>(string const &lhs, - char const *rhs) - -> std::strong_ordering; - -/* * Print a string to a stream. */ export auto operator<<(std::ostream &, string const &) -> std::ostream &; @@ -192,37 +263,37 @@ export auto operator<<(std::ostream &, string const &) -> std::ostream &; * Literal operator. */ inline namespace literals { - export constexpr auto operator""_ucl (char const *s, std::size_t n) - -> string - { - return string(std::string_view(s, n)); - } -} // namespace nihil::ucl::literals +export constexpr auto operator""_ucl(char const *s, std::size_t n) -> string +{ + return string(std::string_view(s, n)); +} +} // namespace literals } // namespace nihil::ucl -namespace nihil { inline namespace literals { - export using namespace ::nihil::ucl::literals; -}} // namespace nihil::literals +namespace nihil { +inline namespace literals { +export using namespace ::nihil::ucl::literals; +} +} // namespace nihil /* * std::formatter for a string. This provides the same format operations * as std::formatter<std::string_view>. */ -export template<> +export template <> struct std::formatter<nihil::ucl::string, char> { std::formatter<std::string_view> base_formatter; - template<class ParseContext> - constexpr ParseContext::iterator parse(ParseContext& ctx) + template <class ParseContext> + constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator { return base_formatter.parse(ctx); } - template<class FmtContext> - FmtContext::iterator format(nihil::ucl::string const &o, - FmtContext& ctx) const + template <class FmtContext> + auto format(nihil::ucl::string const &o, FmtContext &ctx) const -> FmtContext::iterator { return base_formatter.format(o.value(), ctx); } diff --git a/nihil.ucl/tests/string.cc b/nihil.ucl/string.test.cc index 6409b8d..68c57e8 100644 --- a/nihil.ucl/tests/string.cc +++ b/nihil.ucl/string.test.cc @@ -1,18 +1,12 @@ -/* - * This source code is released into the public domain. - */ - -#include <concepts> -#include <list> -#include <sstream> -#include <string> -#include <vector> +// This source code is released into the public domain. #include <catch2/catch_test_macros.hpp> #include <ucl.h> +import nihil.std; import nihil.ucl; +namespace { TEST_CASE("ucl: string: invariants", "[ucl]") { using namespace nihil::ucl; @@ -127,23 +121,23 @@ TEST_CASE("ucl: string: construct from UCL object", "[ucl]") using namespace nihil::ucl; SECTION("ref, correct type") { - auto uobj = ::ucl_object_fromstring("testing"); + auto *uobj = ::ucl_object_fromstring("testing"); - auto s = string(ref, uobj); + auto const s = string(ref, uobj); REQUIRE(s == "testing"); ::ucl_object_unref(uobj); } SECTION("noref, correct type") { - auto uobj = ::ucl_object_fromstring("testing"); + auto *uobj = ::ucl_object_fromstring("testing"); - auto s = string(noref, uobj); + auto const s = string(noref, uobj); REQUIRE(s == "testing"); } SECTION("ref, wrong type") { - auto uobj = ::ucl_object_frombool(true); + auto *uobj = ::ucl_object_frombool(true); REQUIRE_THROWS_AS(string(ref, uobj), type_mismatch); @@ -151,7 +145,7 @@ TEST_CASE("ucl: string: construct from UCL object", "[ucl]") } SECTION("noref, wrong type") { - auto uobj = ::ucl_object_frombool(true); + auto *uobj = ::ucl_object_frombool(true); REQUIRE_THROWS_AS(string(noref, uobj), type_mismatch); @@ -165,53 +159,53 @@ TEST_CASE("ucl: string: make_string", "[ucl]") using namespace std::literals; SECTION("empty string") { - auto str = make_string().value(); + auto const str = make_string().value(); REQUIRE(str.type() == object_type::string); REQUIRE(str == ""); } SECTION("from string literal") { - auto str = make_string("testing").value(); + auto const str = make_string("testing").value(); REQUIRE(str.type() == object_type::string); REQUIRE(str == "testing"); } SECTION("from std::string") { - auto str = make_string("testing"s).value(); + auto const str = make_string("testing"s).value(); REQUIRE(str.type() == object_type::string); REQUIRE(str == "testing"); } SECTION("from std::string_view") { - auto str = make_string("testing"sv).value(); + auto const str = make_string("testing"sv).value(); REQUIRE(str.type() == object_type::string); REQUIRE(str == "testing"); } SECTION("from contiguous range") { - auto s = std::vector{'t', 'e', 's', 't', 'i', 'n', 'g'}; - auto str = make_string(s).value(); + auto const s = std::vector{'t', 'e', 's', 't', 'i', 'n', 'g'}; + auto const str = make_string(s).value(); REQUIRE(str.type() == object_type::string); REQUIRE(str == "testing"); } SECTION("from non-contiguous range") { - auto s = std::list{'t', 'e', 's', 't', 'i', 'n', 'g'}; - auto str = make_string(s).value(); + auto const s = std::list{'t', 'e', 's', 't', 'i', 'n', 'g'}; + auto const str = make_string(s).value(); REQUIRE(str.type() == object_type::string); REQUIRE(str == "testing"); } SECTION("from contiguous iterator pair") { - auto s = std::vector{'t', 'e', 's', 't', 'i', 'n', 'g'}; - auto str = make_string(s.begin(), s.end()).value(); + auto const s = std::vector{'t', 'e', 's', 't', 'i', 'n', 'g'}; + auto const str = make_string(s.begin(), s.end()).value(); REQUIRE(str.type() == object_type::string); REQUIRE(str == "testing"); } SECTION("from non-contiguous iterator pair") { - auto s = std::list{'t', 'e', 's', 't', 'i', 'n', 'g'}; - auto str = make_string(s.begin(), s.end()).value(); + auto const s = std::list{'t', 'e', 's', 't', 'i', 'n', 'g'}; + auto const str = make_string(s.begin(), s.end()).value(); REQUIRE(str.type() == object_type::string); REQUIRE(str == "testing"); } @@ -234,22 +228,19 @@ TEST_CASE("ucl: string: value()", "[ucl]") { using namespace nihil::ucl; - auto s = string("te\"st"); - REQUIRE(s.value() == "te\"st"); + auto const s = string(R"(te"st)"); + REQUIRE(s.value() == R"(te"st)"); } TEST_CASE("ucl: string: key()", "[ucl]") { using namespace nihil::ucl; - auto err = parse("a_string = \"test\""); - REQUIRE(err); - - auto obj = *err; - REQUIRE(object_cast<string>(obj["a_string"])->key() == "a_string"); + auto obj = parse(R"(a_string = "test")").value(); + REQUIRE(object_cast<string>(obj["a_string"]).value().key() == "a_string"); - auto s = string("test"); - REQUIRE(s.key() == ""); + auto const s = string("test"); + REQUIRE(s.key().empty() == true); } TEST_CASE("ucl: string: size", "[ucl]") @@ -280,7 +271,7 @@ TEST_CASE("ucl: string: iterate", "[ucl]") auto end = str.end(); static_assert(std::sentinel_for<decltype(end), - decltype(begin)>); + decltype(begin)>); REQUIRE(*begin == 't'); ++begin; @@ -339,7 +330,7 @@ TEST_CASE("ucl: string: parse", "[ucl]") { using namespace nihil::ucl; - auto obj = parse("value = \"te\\\"st\"").value(); + auto obj = parse(R"(value = "te\"st")").value(); auto v = obj["value"]; REQUIRE(v.key() == "value"); @@ -350,7 +341,7 @@ TEST_CASE("ucl: string: emit", "[ucl]") { using namespace nihil::ucl; - auto ucl = parse("str = \"te\\\"st\";").value(); + auto ucl = parse(R"(str = "te\"st";)").value(); auto output = std::string(); emit(ucl, emitter::configuration, std::back_inserter(output)); @@ -371,7 +362,7 @@ TEST_CASE("ucl: string: format", "[ucl]") } SECTION("parsed string") { - auto obj = parse("string = \"te\\\"st\";").value(); + auto obj = parse(R"(string = "te\"st";)").value(); auto s = object_cast<string>(obj["string"]).value(); auto str = std::format("{}", s); @@ -399,7 +390,7 @@ TEST_CASE("ucl: string: print to ostream", "[ucl]") } SECTION("parsed string") { - auto obj = parse("string = \"te\\\"st\";").value(); + auto obj = parse(R"(string = "te\"st";)").value(); auto s = object_cast<string>(obj["string"]).value(); auto strm = std::ostringstream(); @@ -413,3 +404,4 @@ TEST_CASE("ucl: string: print to ostream", "[ucl]") REQUIRE(str == " te\"st"); } } +} // anonymous namespace diff --git a/nihil.ucl/tests/CMakeLists.txt b/nihil.ucl/tests/CMakeLists.txt deleted file mode 100644 index 13f30fa..0000000 --- a/nihil.ucl/tests/CMakeLists.txt +++ /dev/null @@ -1,20 +0,0 @@ -# This source code is released into the public domain. - -add_executable(nihil.ucl.test - emit.cc - parse.cc - - object.cc - array.cc - boolean.cc - integer.cc - map.cc - real.cc - string.cc -) - -target_link_libraries(nihil.ucl.test PRIVATE nihil.ucl Catch2::Catch2WithMain) - -include(CTest) -include(Catch) -catch_discover_tests(nihil.ucl.test) diff --git a/nihil.ucl/type.cc b/nihil.ucl/type.cc deleted file mode 100644 index 7d9cad7..0000000 --- a/nihil.ucl/type.cc +++ /dev/null @@ -1,62 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include <format> - -module nihil.ucl; - -namespace nihil::ucl { - -auto str(object_type type) -> std::string_view { - using namespace std::literals; - - switch (type) { - case object_type::object: - return "object"sv; - case object_type::array: - return "array"sv; - case object_type::integer: - return "integer"sv; - case object_type::real: - return "real"sv; - case object_type::string: - return "string"sv; - case object_type::boolean: - return "boolean"sv; - case object_type::time: - return "time"sv; - case object_type::userdata: - return "userdata"sv; - case object_type::null: - return "null"sv; - default: - // Don't fail here, since UCL might add more types that we - // don't know about. - return "unknown"sv; - } -} - -type_mismatch::type_mismatch(object_type expected_type, - object_type actual_type) - : error(std::format( - "expected type '{}' != actual type '{}'", - ucl::str(expected_type), ucl::str(actual_type))) - , m_expected_type(expected_type) - , m_actual_type(actual_type) -{ -} - -auto type_mismatch::expected_type(this type_mismatch const &self) -> object_type -{ - return self.m_expected_type; -} - -auto type_mismatch::actual_type(this type_mismatch const &self) -> object_type -{ - return self.m_actual_type; -} - -} // namespace nihil::ucl diff --git a/nihil.ucl/type.ccm b/nihil.ucl/type.ccm index f3b3aef..476546a 100644 --- a/nihil.ucl/type.ccm +++ b/nihil.ucl/type.ccm @@ -1,40 +1,61 @@ -/* - * This source code is released into the public domain. - */ - +// This source code is released into the public domain. module; -#include <concepts> -#include <format> -#include <stdexcept> -#include <string> - #include <ucl.h> export module nihil.ucl:type; +import nihil.std; import nihil.error; namespace nihil::ucl { // Our strongly-typed version of ::ucl_type. -export enum struct object_type { - object = UCL_OBJECT, - array = UCL_ARRAY, - integer = UCL_INT, - real = UCL_FLOAT, - string = UCL_STRING, - boolean = UCL_BOOLEAN, - time = UCL_TIME, - userdata = UCL_USERDATA, - null = UCL_NULL, +export enum struct object_type : std::uint8_t { + object = UCL_OBJECT, + array = UCL_ARRAY, + integer = UCL_INT, + real = UCL_FLOAT, + string = UCL_STRING, + boolean = UCL_BOOLEAN, + time = UCL_TIME, + userdata = UCL_USERDATA, + null = UCL_NULL, }; // Get the name of a type. -export auto str(object_type type) -> std::string_view; +export auto str(object_type type) -> std::string_view +{ + using namespace std::literals; + + switch (type) { + case object_type::object: + return "object"sv; + case object_type::array: + return "array"sv; + case object_type::integer: + return "integer"sv; + case object_type::real: + return "real"sv; + case object_type::string: + return "string"sv; + case object_type::boolean: + return "boolean"sv; + case object_type::time: + return "time"sv; + case object_type::userdata: + return "userdata"sv; + case object_type::null: + return "null"sv; + default: + // Don't fail here, since UCL might add more types that we + // don't know about. + return "unknown"sv; + } +} // Concept of a UCL data type. -export template<typename T> +export template <typename T> concept datatype = requires(T o) { { o.get_ucl_object() } -> std::convertible_to<::ucl_object_t const *>; { o.type() } -> std::same_as<object_type>; @@ -42,14 +63,28 @@ concept datatype = requires(T o) { }; // Exception thrown when a type assertion fails. -export struct type_mismatch : error { - type_mismatch(object_type expected_type, object_type actual_type); +export struct type_mismatch : error +{ + type_mismatch(object_type expected_type, object_type actual_type) + : error(std::format("expected type '{}' != actual type '{}'", + ucl::str(expected_type), ucl::str(actual_type))) + , m_expected_type(expected_type) + , m_actual_type(actual_type) + { + } // The type we expected. - auto expected_type(this type_mismatch const &self) -> object_type; + auto expected_type(this type_mismatch const &self) -> object_type + { + return self.m_expected_type; + } + // The type we got. - auto actual_type(this type_mismatch const &self) -> object_type; - + auto actual_type(this type_mismatch const &self) -> object_type + { + return self.m_actual_type; + } + private: object_type m_expected_type; object_type m_actual_type; |
