From 2e2d1bd3b6c7776b77c33b94f30ead89367a71e6 Mon Sep 17 00:00:00 2001 From: Lexi Winter Date: Tue, 1 Jul 2025 17:07:04 +0100 Subject: add nihil.std --- nihil.ucl/CMakeLists.txt | 34 +-- nihil.ucl/array.ccm | 424 +++++++++++++++--------------------- nihil.ucl/array.test.cc | 479 +++++++++++++++++++++++++++++++++++++++++ nihil.ucl/boolean.cc | 106 --------- nihil.ucl/boolean.ccm | 138 +++++++----- nihil.ucl/boolean.test.cc | 241 +++++++++++++++++++++ nihil.ucl/emit.cc | 21 -- nihil.ucl/emit.ccm | 179 ++++++++------- nihil.ucl/emit.test.cc | 92 ++++++++ nihil.ucl/errc.cc | 49 ----- nihil.ucl/errc.ccm | 33 --- nihil.ucl/integer.cc | 102 --------- nihil.ucl/integer.ccm | 165 ++++++++------ nihil.ucl/integer.test.cc | 244 +++++++++++++++++++++ nihil.ucl/map.ccm | 263 ++++++++++------------ nihil.ucl/map.test.cc | 192 +++++++++++++++++ nihil.ucl/nihil.ucl.ccm | 8 +- nihil.ucl/object.cc | 114 ---------- nihil.ucl/object.ccm | 182 +++++++++++----- nihil.ucl/object.test.cc | 43 ++++ nihil.ucl/object_cast.ccm | 19 +- nihil.ucl/parse.test.cc | 55 +++++ nihil.ucl/parser.cc | 102 --------- nihil.ucl/parser.ccm | 158 ++++++++------ nihil.ucl/real.cc | 104 --------- nihil.ucl/real.ccm | 151 +++++++------ nihil.ucl/real.test.cc | 245 +++++++++++++++++++++ nihil.ucl/string.cc | 187 ---------------- nihil.ucl/string.ccm | 357 ++++++++++++++++++------------ nihil.ucl/string.test.cc | 407 ++++++++++++++++++++++++++++++++++ nihil.ucl/tests/CMakeLists.txt | 20 -- nihil.ucl/tests/array.cc | 478 ---------------------------------------- nihil.ucl/tests/boolean.cc | 224 ------------------- nihil.ucl/tests/emit.cc | 93 -------- nihil.ucl/tests/integer.cc | 247 --------------------- nihil.ucl/tests/map.cc | 192 ----------------- nihil.ucl/tests/object.cc | 44 ---- nihil.ucl/tests/parse.cc | 55 ----- nihil.ucl/tests/real.cc | 248 --------------------- nihil.ucl/tests/string.cc | 415 ----------------------------------- nihil.ucl/type.cc | 62 ------ nihil.ucl/type.ccm | 87 +++++--- 42 files changed, 3171 insertions(+), 3888 deletions(-) create mode 100644 nihil.ucl/array.test.cc delete mode 100644 nihil.ucl/boolean.cc create mode 100644 nihil.ucl/boolean.test.cc delete mode 100644 nihil.ucl/emit.cc create mode 100644 nihil.ucl/emit.test.cc delete mode 100644 nihil.ucl/errc.cc delete mode 100644 nihil.ucl/errc.ccm delete mode 100644 nihil.ucl/integer.cc create mode 100644 nihil.ucl/integer.test.cc create mode 100644 nihil.ucl/map.test.cc delete mode 100644 nihil.ucl/object.cc create mode 100644 nihil.ucl/object.test.cc create mode 100644 nihil.ucl/parse.test.cc delete mode 100644 nihil.ucl/parser.cc delete mode 100644 nihil.ucl/real.cc create mode 100644 nihil.ucl/real.test.cc delete mode 100644 nihil.ucl/string.cc create mode 100644 nihil.ucl/string.test.cc delete mode 100644 nihil.ucl/tests/CMakeLists.txt delete mode 100644 nihil.ucl/tests/array.cc delete mode 100644 nihil.ucl/tests/boolean.cc delete mode 100644 nihil.ucl/tests/emit.cc delete mode 100644 nihil.ucl/tests/integer.cc delete mode 100644 nihil.ucl/tests/map.cc delete mode 100644 nihil.ucl/tests/object.cc delete mode 100644 nihil.ucl/tests/parse.cc delete mode 100644 nihil.ucl/tests/real.cc delete mode 100644 nihil.ucl/tests/string.cc delete mode 100644 nihil.ucl/type.cc (limited to 'nihil.ucl') 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 -#include -#include -#include -#include -#include -#include -#include -#include - #include export module nihil.ucl:array; +import nihil.std; import :object; namespace nihil::ucl { -export template +export template struct array; -export template -struct array_iterator { +// +// The array iterator. This is hardened, i.e. it always checks bounds. +// +export template +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; - ::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 [[nodiscard]] -auto operator+(array_iterator const &lhs, - typename array_iterator::difference_type rhs) --> array_iterator -{ - auto copy = lhs; - copy += rhs; - return copy; -} - -export template [[nodiscard]] -auto operator+(typename array_iterator::difference_type lhs, - array_iterator const &rhs) - -> array_iterator -{ - return rhs - lhs; -} + [[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 [[nodiscard]] -auto operator-(array_iterator const &lhs, - typename array_iterator::difference_type rhs) - -> array_iterator -{ - 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 -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 +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; - /* - * 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( - ::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( - ::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 - requires(std::convertible_to, T>) - array(Iterator first, Iterator last) - : array() + // Create an array from a range. + template + requires(std::convertible_to, 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 - requires(std::convertible_to, T>) - array(std::from_range_t, Range &&range) - : array(std::ranges::begin(range), - std::ranges::end(range)) + // Create an array from an iterator pair. + template + requires(std::convertible_to, 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 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 [[nodiscard]] -auto operator==(array const &a, array const &b) -> bool -{ - if (a.size() != b.size()) - return false; +private: + // + // Comparison operators. + // - for (typename array::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 -auto operator<<(std::ostream &strm, array 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 +// std::formatter for an array. The output format is a list of values +// on a single line: [1, 2, 3]. +export template struct std::formatter, char> { - template - constexpr ParseContext::iterator parse(ParseContext& ctx) + template + constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator { + (void)this; return ctx.begin(); } - template - FmtContext::iterator format(nihil::ucl::array const &o, - FmtContext& ctx) const + template + auto format(nihil::ucl::array const &o, FmtContext &ctx) const -> FmtContext::iterator { auto it = ctx.out(); bool first = true; diff --git a/nihil.ucl/array.test.cc b/nihil.ucl/array.test.cc new file mode 100644 index 0000000..89394a0 --- /dev/null +++ b/nihil.ucl/array.test.cc @@ -0,0 +1,479 @@ +// This source code is released into the public domain. + +#include +#include + +import nihil.std; +import nihil.ucl; + +namespace { +TEST_CASE("ucl: array: invariants", "[ucl]") +{ + using namespace nihil::ucl; + + REQUIRE(array<>::ucl_type == object_type::array); + REQUIRE(static_cast<::ucl_type>(array<>::ucl_type) == UCL_ARRAY); + + static_assert(std::destructible>); + static_assert(std::default_initializable>); + static_assert(std::move_constructible>); + static_assert(std::copy_constructible>); + static_assert(std::equality_comparable>); + static_assert(std::totally_ordered>); + static_assert(std::swappable>); + + static_assert(std::ranges::sized_range>); + static_assert(std::same_as>, integer>); +} + +TEST_CASE("ucl: array: constructor", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION("default") + { + auto arr = array(); + REQUIRE(arr.size() == 0); + REQUIRE(str(arr.type()) == "array"); + } + + SECTION("from range") + { + auto vec = std::vector{integer(1), integer(42)}; + auto arr = array(vec); + + REQUIRE(arr.size() == 2); + REQUIRE(arr[0] == 1); + REQUIRE(arr[1] == 42); + } + + SECTION("from iterator pair") + { + auto vec = std::vector{integer(1), integer(42)}; + auto arr = array(std::ranges::begin(vec), std::ranges::end(vec)); + + REQUIRE(arr.size() == 2); + REQUIRE(arr[0] == 1); + REQUIRE(arr[1] == 42); + } + + SECTION("from initializer_list") + { + auto arr = array{integer(1), integer(42)}; + + REQUIRE(arr.size() == 2); + REQUIRE(arr[0] == 1); + REQUIRE(arr[1] == 42); + } +} + +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); + ::ucl_array_append(uarr, uint); + + auto arr = array(ref, uarr); + REQUIRE(arr[0] == 42); + + ::ucl_object_unref(uarr); + } + + 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(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); + ::ucl_array_append(uarr, uint); + + auto arr = array(noref, uarr); + REQUIRE_THROWS_AS(arr[0], type_mismatch); + } + + 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); + + REQUIRE_THROWS_AS(array(noref, uobj), type_mismatch); + + ::ucl_object_unref(uobj); + } +} + +TEST_CASE("ucl: array: swap", "[ucl]") +{ + // do not add using namespace nihil::ucl + + auto arr1 = nihil::ucl::array{nihil::ucl::integer(1), + nihil::ucl::integer(2)}; + + auto arr2 = nihil::ucl::array{ + nihil::ucl::integer(3), + }; + + swap(arr1, arr2); + + REQUIRE(arr1.size() == 1); + REQUIRE(arr1[0] == 3); + + REQUIRE(arr2.size() == 2); + REQUIRE(arr2[0] == 1); +} + +TEST_CASE("ucl: array: push_back", "[ucl]") +{ + using namespace nihil::ucl; + + auto arr = array(); + REQUIRE(arr.size() == 0); + + arr.push_back(integer(1)); + arr.push_back(integer(42)); + arr.push_back(integer(666)); + + REQUIRE(arr.size() == 3); + REQUIRE(arr[0] == 1); + REQUIRE(arr[1] == 42); + REQUIRE(arr[2] == 666); + + REQUIRE_THROWS_AS(arr[3], std::out_of_range); + + REQUIRE(arr.front() == 1); + REQUIRE(arr.back() == 666); +} + +TEST_CASE("ucl: array: compare", "[ucl]") +{ + using namespace nihil::ucl; + + auto arr = array{integer(1), integer(42), integer(666)}; + + auto arr2 = array(); + REQUIRE(arr != arr2); + + arr2.push_back(integer(1)); + arr2.push_back(integer(42)); + arr2.push_back(integer(666)); + REQUIRE(arr == arr2); + + auto arr3 = array{integer(1), integer(1), integer(1)}; + + REQUIRE(arr != arr3); +} + +TEST_CASE("ucl: array: iterator", "[ucl]") +{ + using namespace nihil::ucl; + + auto arr = array{integer(1), integer(42), integer(666)}; + + auto it = arr.begin(); + REQUIRE(*it == 1); + auto end = arr.end(); + REQUIRE(it != end); + REQUIRE(it < end); + + ++it; + REQUIRE(*it == 42); + + ++it; + REQUIRE(*it == 666); + + --it; + REQUIRE(*it == 42); + + ++it; + REQUIRE(it != end); + ++it; + REQUIRE(it == end); +} + +TEST_CASE("ucl: array: parse", "[ucl]") +{ + using namespace std::literals; + using namespace nihil::ucl; + + auto obj = parse("value = [1, 42, 666]"sv).value(); + + auto arr = object_cast>(obj["value"]).value(); + + REQUIRE(arr.size() == 3); + REQUIRE(arr[0] == 1); + REQUIRE(arr[1] == 42); + REQUIRE(arr[2] == 666); +} + +TEST_CASE("ucl: array: emit", "[ucl]") +{ + using namespace nihil::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"); +} + +TEST_CASE("ucl: array: format", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION("empty array") + { + auto arr = array(); + REQUIRE(std::format("{}", arr) == "[]"); + } + + SECTION("bare array") + { + auto arr = array{integer(1), integer(42), integer(666)}; + + auto output = std::format("{}", arr); + REQUIRE(output == "[1, 42, 666]"); + } + + SECTION("parsed array") + { + auto ucl = parse("array = [1, 42, 666];").value(); + auto arr = object_cast>(ucl["array"]).value(); + + auto output = std::format("{}", arr); + REQUIRE(output == "[1, 42, 666]"); + } +} + +TEST_CASE("ucl: array: print to ostream", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION("empty array") + { + auto arr = array(); + auto strm = std::ostringstream(); + strm << arr; + + REQUIRE(strm.str() == "[]"); + } + + SECTION("bare array") + { + auto arr = array{integer(1), integer(42), integer(666)}; + auto strm = std::ostringstream(); + strm << arr; + + REQUIRE(strm.str() == "[1, 42, 666]"); + } + + SECTION("parsed array") + { + auto ucl = parse("array = [1, 42, 666];").value(); + auto arr = object_cast>(ucl["array"]).value(); + auto strm = std::ostringstream(); + strm << arr; + + REQUIRE(strm.str() == "[1, 42, 666]"); + } +} + +TEST_CASE("ucl: array is a sized_range", "[ucl]") +{ + using namespace nihil::ucl; + + auto arr = array{integer(1), integer(42), integer(666)}; + + auto size = std::ranges::size(arr); + REQUIRE(size == 3); + + auto begin = std::ranges::begin(arr); + static_assert(std::random_access_iterator); + + auto end = std::ranges::end(arr); + static_assert(std::sentinel_for); + + REQUIRE(std::distance(begin, end) == 3); + + auto vec = std::vector(); + 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 int_vec = std::vector(); + std::ranges::copy(arr_as_ints, std::back_inserter(int_vec)); + REQUIRE(int_vec == std::vector{1, 42, 666}); +} + +TEST_CASE("ucl: array: bad object_cast", "[ucl]") +{ + using namespace nihil::ucl; + + auto arr = array(); + + auto cast_ok = object_cast(arr); + REQUIRE(!cast_ok); +} + +TEST_CASE("ucl: array: heterogeneous elements", "[ucl]") +{ + using namespace std::literals; + using namespace nihil::ucl; + + auto obj_err = parse("array [ 42, true, \"test\" ];"); + REQUIRE(obj_err); + auto obj = *obj_err; + + auto err = object_cast>(obj["array"]); + REQUIRE(err); + + auto arr = *err; + REQUIRE(arr.size() == 3); + + auto int_obj = object_cast(arr[0]); + REQUIRE(int_obj); + REQUIRE(*int_obj == 42); + + auto bool_obj = object_cast(arr[1]); + REQUIRE(bool_obj); + REQUIRE(*bool_obj == true); + + auto string_obj = object_cast(arr[2]); + REQUIRE(string_obj); + REQUIRE(*string_obj == "test"); +} + +TEST_CASE("ucl: array: heterogenous cast", "[ucl]") +{ + using namespace nihil::ucl; + + auto arr = array<>(); + arr.push_back(integer(42)); + arr.push_back(boolean(true)); + + // Converting to an array should fail. + auto cast_ok = object_cast>(arr); + REQUIRE(!cast_ok); + + // Converting to array should succeed. + auto err = object_cast>(arr); + REQUIRE(err); + + auto obj_arr = *err; + REQUIRE(obj_arr[0] == integer(42)); +} + +TEST_CASE("ucl: array: homogeneous cast", "[ucl]") +{ + using namespace nihil::ucl; + + auto arr = array<>(); + arr.push_back(integer(1)); + arr.push_back(integer(42)); + + auto obj = object(ref, arr.get_ucl_object()); + + // Converting to array should fail. + auto cast_ok = object_cast>(obj); + REQUIRE(!cast_ok); + + // Converting to an array should succeed. + auto err = object_cast>(obj); + REQUIRE(err); + + auto obj_arr = *err; + REQUIRE(obj_arr[0] == 1); + REQUIRE(obj_arr[1] == 42); +} + +TEST_CASE("array iterator: empty iterator", "[ucl]") +{ + using namespace nihil::ucl; + + auto it = array_iterator(); + + REQUIRE_THROWS_AS(*it, std::logic_error); + REQUIRE_THROWS_AS(it[0], std::logic_error); + REQUIRE_THROWS_AS(it++, std::logic_error); + REQUIRE_THROWS_AS(++it, std::logic_error); + + auto it2 = array_iterator(); + REQUIRE(it == it2); + REQUIRE((it < it2) == false); + REQUIRE((it > it2) == false); +} + +TEST_CASE("array iterator: invalid operations", "[ucl]") +{ + using namespace nihil::ucl; + + auto arr = array{integer(42)}; + auto it = arr.begin(); + + 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") + { + ++it; + REQUIRE(it == arr.end()); + + REQUIRE_THROWS_AS(++it, std::logic_error); + REQUIRE_THROWS_AS(it++, std::logic_error); + REQUIRE_THROWS_AS(it + 1, std::logic_error); + } + + SECTION("dereference iterator at end") + { + REQUIRE_THROWS_AS(it[1], std::logic_error); + + ++it; + REQUIRE(it == arr.end()); + + REQUIRE_THROWS_AS(*it, std::logic_error); + } + + SECTION("compare with different array") + { + auto arr2 = array{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") + { + auto it2 = array_iterator(); + 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 -#include -#include -#include - -#include - -module nihil.ucl; - -import nihil.error; - -namespace nihil::ucl { - -auto make_boolean(boolean::contained_type value) - -> std::expected -{ - 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( - ::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( - ::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 -#include -#include -#include -#include -#include - #include 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; + 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(a.value()) <=> static_cast(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(a.value()) <=> static_cast(b); + } +}; + +// Boolean constructors. This returns an error instead of throwing. +export [[nodiscard]] auto +make_boolean(boolean::contained_type const value = false) -> std::expected +{ + 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. - */ -export template<> +// std::formatter for a boolean. This provides the same format operations +// as std::formatter. +export template <> struct std::formatter { std::formatter base_formatter; - template - constexpr ParseContext::iterator parse(ParseContext& ctx) + template + constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator { return base_formatter.parse(ctx); } - template - FmtContext::iterator format(nihil::ucl::boolean const &o, - FmtContext& ctx) const + template + auto format(nihil::ucl::boolean const &o, FmtContext &ctx) const -> FmtContext::iterator { return base_formatter.format(o.value(), ctx); } diff --git a/nihil.ucl/boolean.test.cc b/nihil.ucl/boolean.test.cc new file mode 100644 index 0000000..9fb0148 --- /dev/null +++ b/nihil.ucl/boolean.test.cc @@ -0,0 +1,241 @@ +// This source code is released into the public domain. + +#include +#include + +import nihil.std; +import nihil.ucl; + +namespace { +inline auto constexpr *test_tags = "[nihil][nihil.ucl][nihil.ucl.boolean]"; + +TEST_CASE("ucl: boolean: invariants", test_tags) +{ + using namespace nihil::ucl; + + static_assert(std::same_as); + REQUIRE(boolean::ucl_type == object_type::boolean); + REQUIRE(static_cast<::ucl_type>(boolean::ucl_type) == UCL_BOOLEAN); + + static_assert(std::destructible); + static_assert(std::default_initializable); + static_assert(std::move_constructible); + static_assert(std::copy_constructible); + static_assert(std::equality_comparable); + static_assert(std::totally_ordered); + static_assert(std::swappable); +} + +SCENARIO("Constructing a ucl::boolean", test_tags) +{ + using namespace nihil::ucl; + + GIVEN ("A default-constructed boolean") { + auto b = boolean(); + + THEN ("The value is false") { + REQUIRE(b == false); + } + } + + GIVEN ("A boolean constructed from a true value") { + auto b = boolean(true); + + THEN ("The value is true") { + REQUIRE(b == true); + } + } +} + +TEST_CASE("ucl: boolean: construct from UCL object", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION("ref, correct type") + { + auto *uobj = ::ucl_object_frombool(true); + + auto i = boolean(ref, uobj); + REQUIRE(i == true); + + ::ucl_object_unref(uobj); + } + + 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); + + REQUIRE_THROWS_AS(boolean(ref, uobj), type_mismatch); + + ::ucl_object_unref(uobj); + } + + SECTION("noref, wrong type") + { + auto *uobj = ::ucl_object_fromint(1); + + REQUIRE_THROWS_AS(boolean(noref, uobj), type_mismatch); + + ::ucl_object_unref(uobj); + } +} + +TEST_CASE("ucl: boolean: make_boolean", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION("default value") + { + auto b = make_boolean().value(); + REQUIRE(b == false); + } + + SECTION("explicit value") + { + auto b = make_boolean(true).value(); + REQUIRE(b == true); + } +} + +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); + + swap(b1, b2); + + REQUIRE(b1 == false); + REQUIRE(b2 == true); +} + +TEST_CASE("ucl: boolean: value()", "[ucl]") +{ + auto b = nihil::ucl::boolean(true); + REQUIRE(b.value() == true); +} + +TEST_CASE("ucl: boolean: key()", "[ucl]") +{ + using namespace nihil::ucl; + + auto obj = parse("a_bool = true").value(); + REQUIRE(object_cast(obj["a_bool"]).value().key() == "a_bool"); + + auto b = boolean(true); + REQUIRE(b.key().empty() == true); +} + +TEST_CASE("ucl: boolean: comparison", "[ucl]") +{ + using namespace nihil::ucl; + + auto b = boolean(true); + + SECTION("operator==") + { + REQUIRE(b == true); + REQUIRE(b == boolean(true)); + } + + SECTION("operator!=") + { + REQUIRE(b != false); + REQUIRE(b != boolean(false)); + } + + SECTION("operator<") + { + REQUIRE(b <= true); + REQUIRE(b <= nihil::ucl::boolean(true)); + } + + SECTION("operator>") + { + REQUIRE(b > false); + REQUIRE(b > nihil::ucl::boolean(false)); + } +} + +TEST_CASE("ucl: boolean: parse", "[ucl]") +{ + using namespace nihil::ucl; + + auto obj = parse("value = true").value(); + + auto v = obj["value"]; + REQUIRE(v.key() == "value"); + REQUIRE(object_cast(v).value() == true); +} + +TEST_CASE("ucl: boolean: parse and emit", "[ucl]") +{ + using namespace nihil::ucl; + + auto ucl = parse("bool = true;").value(); + + auto output = std::string(); + emit(ucl, nihil::ucl::emitter::configuration, std::back_inserter(output)); + + REQUIRE(output == "bool = true;\n"); +} + +TEST_CASE("ucl: boolean: format", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION("bare boolean") + { + auto str = std::format("{}", boolean(true)); + REQUIRE(str == "true"); + } + + SECTION("parsed boolean") + { + auto obj = parse("bool = true;").value(); + auto b = object_cast(obj["bool"]).value(); + + auto str = std::format("{}", b); + REQUIRE(str == "true"); + } + + SECTION("with format string") + { + auto str = std::format("{: >5}", boolean(true)); + REQUIRE(str == " true"); + } +} + +TEST_CASE("ucl: boolean: print to ostream", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION("bare boolean") + { + auto strm = std::ostringstream(); + strm << boolean(true); + + REQUIRE(strm.str() == "true"); + } + + SECTION("parsed boolean") + { + auto obj = parse("bool = true;").value(); + auto i = object_cast(obj["bool"]).value(); + + auto strm = std::ostringstream(); + strm << i; + + 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 -#include - -module nihil.ucl; - -namespace nihil::ucl { - -auto operator<<(std::ostream &stream, object const &o) --> std::ostream & -{ - emit(o, emitter::json, std::ostream_iterator(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 -#include -#include -#include -#include -#include -#include -#include -#include - #include 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 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(ud); - - while (nchars--) - *self->iterator++ = static_cast(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 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(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(ud); - - for (auto c : std::span(str, len)) - *self->iterator++ = static_cast(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::digits10; - auto buf = std::array(); + auto &self = check_magic(ud); - auto *self = static_cast(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::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::digits10; - auto buf = std::array(); - - auto *self = static_cast(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::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(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 auto &&it) - -> void +export auto +emit(object const &object, emitter const format, std::output_iterator auto &&it) -> void { auto ucl_format = static_cast(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(stream)); + return stream; +} } // namespace nihil::ucl -/* - * Specialisation of std::formatter<> for object. - */ -template T> +// Specialisation of std::formatter<> for object. Note that most derived +// UCL types override this. +template T> struct std::formatter { nihil::ucl::emitter emitter = nihil::ucl::emitter::json; - template - constexpr ParseContext::iterator parse(ParseContext& ctx) + template + constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator { auto it = ctx.begin(); auto end = ctx.end(); @@ -179,8 +173,7 @@ struct std::formatter 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 return it; } - template - FmtContext::iterator format(nihil::ucl::object const &o, - FmtContext& ctx) const + template + 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 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/emit.test.cc b/nihil.ucl/emit.test.cc new file mode 100644 index 0000000..51c4e0e --- /dev/null +++ b/nihil.ucl/emit.test.cc @@ -0,0 +1,92 @@ +// This source code is released into the public domain. + +#include + +import nihil.std; +import nihil.ucl; + +namespace { +TEST_CASE("ucl: emit to std::ostream", "[ucl]") +{ + using namespace std::literals; + + auto obj = nihil::ucl::parse("int = [1, 42, 666]"sv); + REQUIRE(obj); + + auto strm = std::ostringstream(); + strm << *obj; + + // The ostream emitter produces JSON. + REQUIRE(strm.str() == std::format("{:j}", *obj)); +} + +TEST_CASE("ucl: emit JSON with std::format", "[ucl]") +{ + using namespace std::literals; + + auto obj = nihil::ucl::parse("int = [1, 42, 666]"sv); + REQUIRE(obj); + + auto str = std::format("{:j}", *obj); + + REQUIRE(str == +"{\n" +" \"int\": [\n" +" 1,\n" +" 42,\n" +" 666\n" +" ]\n" +"}"); + + // Make sure JSON is the default format. + auto str2 = std::format("{}", *obj); + REQUIRE(str == str2); +} + +TEST_CASE("ucl: emit compact JSON with std::format", "[ucl]") +{ + using namespace std::literals; + + auto obj = nihil::ucl::parse("int = [1, 42, 666]"sv); + REQUIRE(obj); + + auto str = std::format("{:J}", *obj); + + REQUIRE(str == "{\"int\":[1,42,666]}"); +} + +TEST_CASE("ucl: emit configuration with std::format", "[ucl]") +{ + using namespace std::literals; + + auto obj = nihil::ucl::parse("int = [1, 42, 666]"sv); + REQUIRE(obj); + + auto str = std::format("{:c}", *obj); + + REQUIRE(str == +"int [\n" +" 1,\n" +" 42,\n" +" 666,\n" +"]\n"); +} + +TEST_CASE("ucl: emit YAML with std::format", "[ucl]") +{ + using namespace std::literals; + + auto obj = nihil::ucl::parse("int = [1, 42, 666]"sv); + REQUIRE(obj); + + auto str = std::format("{:y}", *obj); + + REQUIRE(str == +"int: [\n" +" 1,\n" +" 42,\n" +" 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 -#include - -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(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(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 -#include - -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 : 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 -#include -#include -#include - -#include - -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( - ::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( - ::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 -{ - 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 -#include -#include -#include -#include -#include - #include 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; + 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 +{ + 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::max())) throw std::out_of_range("literal out of range"); return integer(static_cast(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. - */ -export template<> +// std::formatter for an integer. This provides the same format operations +// as std::formatter. +export template <> struct std::formatter { std::formatter base_formatter; - template - constexpr ParseContext::iterator parse(ParseContext& ctx) + template + constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator { return base_formatter.parse(ctx); } - template - FmtContext::iterator format(nihil::ucl::integer const &o, - FmtContext& ctx) const + template + auto format(nihil::ucl::integer const &o, FmtContext &ctx) const -> FmtContext::iterator { return base_formatter.format(o.value(), ctx); } diff --git a/nihil.ucl/integer.test.cc b/nihil.ucl/integer.test.cc new file mode 100644 index 0000000..68567e9 --- /dev/null +++ b/nihil.ucl/integer.test.cc @@ -0,0 +1,244 @@ +// This source code is released into the public domain. + +#include +#include + +import nihil.std; +import nihil.ucl; + +namespace { +TEST_CASE("ucl: integer: invariants", "[ucl]") +{ + using namespace nihil::ucl; + + static_assert(std::same_as); + REQUIRE(integer::ucl_type == object_type::integer); + REQUIRE(static_cast<::ucl_type>(integer::ucl_type) == UCL_INT); + + static_assert(std::destructible); + static_assert(std::default_initializable); + static_assert(std::move_constructible); + static_assert(std::copy_constructible); + static_assert(std::equality_comparable); + static_assert(std::totally_ordered); + static_assert(std::swappable); +} + +TEST_CASE("ucl: integer: constructor", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION("default") { + auto i = integer(); + REQUIRE(i == 0); + } + + SECTION("with value") { + auto i = integer(42); + REQUIRE(i == 42); + } +} + +TEST_CASE("ucl: integer: literal", "[ucl]") +{ + SECTION("with namespace nihil::ucl::literals") { + using namespace nihil::ucl::literals; + + auto i = 42_ucl; + REQUIRE(i.type() == nihil::ucl::object_type::integer); + REQUIRE(i == 42); + } + + SECTION("with namespace nihil::literals") { + using namespace nihil::literals; + + auto i = 42_ucl; + REQUIRE(i.type() == nihil::ucl::object_type::integer); + REQUIRE(i == 42); + } +} + +TEST_CASE("ucl: integer: construct from UCL object", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION("ref, correct type") { + auto *uobj = ::ucl_object_fromint(42); + + auto i = integer(ref, uobj); + REQUIRE(i == 42); + + ::ucl_object_unref(uobj); + } + + SECTION("noref, correct type") { + auto *uobj = ::ucl_object_fromint(42); + + auto i = integer(noref, uobj); + REQUIRE(i == 42); + } + + SECTION("ref, wrong type") { + auto *uobj = ::ucl_object_frombool(true); + + REQUIRE_THROWS_AS(integer(ref, uobj), type_mismatch); + + ::ucl_object_unref(uobj); + } + + SECTION("noref, wrong type") { + auto *uobj = ::ucl_object_frombool(true); + + REQUIRE_THROWS_AS(integer(noref, uobj), type_mismatch); + + ::ucl_object_unref(uobj); + } +} + +TEST_CASE("ucl: integer: make_integer", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION("default value") { + auto i = make_integer().value(); + REQUIRE(i == 0); + } + + SECTION("explicit value") { + auto i = make_integer(42).value(); + REQUIRE(i == 42); + } +} + +TEST_CASE("ucl: integer: swap", "[ucl]") +{ + // do not add using namespace nihil::ucl + + auto i1 = nihil::ucl::integer(1); + auto i2 = nihil::ucl::integer(2); + + swap(i1, i2); + + REQUIRE(i1 == 2); + REQUIRE(i2 == 1); +} + +TEST_CASE("ucl: integer: value()", "[ucl]") +{ + using namespace nihil::ucl; + + auto i = 42_ucl; + REQUIRE(i.value() == 42); +} + +TEST_CASE("ucl: integer: key()", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION("parsed with key") { + auto obj = parse("an_int = 42").value(); + auto i = object_cast(obj["an_int"]).value(); + REQUIRE(i.key() == "an_int"); + } + + SECTION("bare integer, no key") { + auto i = 42_ucl; + REQUIRE(i.key().empty() == true); + } +} + +TEST_CASE("ucl: integer: comparison", "[ucl]") +{ + using namespace nihil::ucl; + + auto i = 42_ucl; + + SECTION("operator==") { + REQUIRE(i == 42); + REQUIRE(i == 42_ucl); + } + + SECTION("operator!=") { + REQUIRE(i != 1); + REQUIRE(i != 1_ucl); + } + + SECTION("operator<") { + REQUIRE(i < 43); + REQUIRE(i < 43_ucl); + } + + SECTION("operator>") { + REQUIRE(i > 1); + REQUIRE(i > 1_ucl); + } +} + +TEST_CASE("ucl: integer: parse", "[ucl]") +{ + using namespace nihil::ucl; + + auto obj = parse("value = 42").value(); + + auto v = obj["value"]; + REQUIRE(v.key() == "value"); + REQUIRE(object_cast(v) == 42); +} + +TEST_CASE("ucl: integer: parse and emit", "[ucl]") +{ + using namespace nihil::ucl; + + auto ucl = parse("int = 42;").value(); + + auto output = std::string(); + emit(ucl, emitter::configuration, std::back_inserter(output)); + + REQUIRE(output == "int = 42;\n"); +} + +TEST_CASE("ucl: integer: format", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION("bare integer") { + auto str = std::format("{}", 42_ucl); + REQUIRE(str == "42"); + } + + SECTION("parsed integer") { + auto obj = parse("int = 42;").value(); + auto i = object_cast(obj["int"]).value(); + + auto str = std::format("{}", i); + REQUIRE(str == "42"); + } + + SECTION("with format string") { + auto str = std::format("{:-05}", 42_ucl); + REQUIRE(str == "00042"); + } +} + +TEST_CASE("ucl: integer: print to ostream", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION("bare integer") { + auto strm = std::ostringstream(); + strm << 42_ucl; + + REQUIRE(strm.str() == "42"); + } + + SECTION("parsed integer") { + auto obj = parse("int = 42;").value(); + auto i = object_cast(obj["int"]).value(); + + auto strm = std::ostringstream(); + strm << i; + + 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 -#include -#include -#include -#include -#include -#include -#include - #include export module nihil.ucl:map; +import nihil.std; import :object; namespace nihil::ucl { // Exception thrown when map::operator[] does not find the key. -export struct key_not_found : error { - 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 +export template struct map; -template -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 +struct map_iterator +{ using difference_type = std::ptrdiff_t; using value_type = std::pair; 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; - map_iterator(::ucl_object_t const *obj) + explicit map_iterator(::ucl_object_t const *obj) : m_state(std::make_shared(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 m_state; }; -export template -struct map final : object { - inline static constexpr object_type ucl_type = object_type::object; +export template +struct map final : object +{ + static constexpr auto ucl_type = object_type::object; using value_type = std::pair; using size_type = std::size_t; using difference_type = std::ptrdiff_t; using iterator = map_iterator; + using sentinel = typename iterator::sentinel; - /* - * 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( - ::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( - ::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 - requires(std::convertible_to, value_type>) - map(Iterator first, Iterator last) - : map() + // Create a map from a range of value types. + template + requires(std::convertible_to, value_type>) + explicit map(Range const &range) + : map() { // This is exception safe, because if we throw here the // base class destructor will free the map. - while (first != last) { - insert(*first); - ++first; - } + for (auto &&v: range) + insert(v); } - /* - * Create a map from a range. - */ - template - requires(std::convertible_to, - value_type>) - map(std::from_range_t, Range &&range) - : map(std::ranges::begin(range), - std::ranges::end(range)) + // Create a map from an iterator pair. + template + requires(std::convertible_to, value_type>) + map(Iterator first, Iterator last) + : map(std::ranges::subrange(first, last)) { } - /* - * Create a map from an initializer_list. - */ + // Create a map from an initializer_list. map(std::initializer_list 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 + // Access a map element by key. + [[nodiscard]] auto find(this map const &self, std::string_view const key) -> std::optional { - auto const *obj = ::ucl_object_lookup_len( - self.get_ucl_object(), - key.data(), key.size()); + 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 + // Remove an object from the map and return it. If the map is empty, returns nullopt. + auto pop(this map &self, std::string_view const key) -> std::optional { - auto *uobj = ::ucl_object_pop_keyl(self.get_ucl_object(), - key.data(), key.size()); + 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/map.test.cc b/nihil.ucl/map.test.cc new file mode 100644 index 0000000..6d31af2 --- /dev/null +++ b/nihil.ucl/map.test.cc @@ -0,0 +1,192 @@ +// This source code is released into the public domain. + +#include +#include + +import nihil.std; +import nihil.ucl; + +//NOLINTBEGIN(bugprone-unchecked-optional-access) + +namespace { +TEST_CASE("ucl: map: invariants", "[ucl]") +{ + using namespace nihil::ucl; + + REQUIRE(map<>::ucl_type == object_type::object); + REQUIRE(static_cast<::ucl_type>(map<>::ucl_type) == UCL_OBJECT); + + static_assert(std::destructible>); + static_assert(std::default_initializable>); + static_assert(std::move_constructible>); + static_assert(std::copy_constructible>); + static_assert(std::equality_comparable>); + static_assert(std::totally_ordered>); + static_assert(std::swappable>); + + static_assert(std::ranges::range>); + static_assert(std::same_as, + std::ranges::range_value_t>>); +} + +TEST_CASE("ucl: map: default construct", "[ucl]") +{ + auto map = nihil::ucl::map<>(); + REQUIRE(str(map.type()) == "object"); +} + +TEST_CASE("ucl: map: construct from initializer_list", "[ucl]") +{ + using namespace nihil::ucl; + using namespace std::literals; + + auto map = nihil::ucl::map{ + {"1"sv, integer(1)}, + {"42"sv, integer(42)}, + }; + + REQUIRE(str(map.type()) == "object"); + REQUIRE(map["1"] == 1); + REQUIRE(map["42"] == 42); +} + +TEST_CASE("ucl: map: construct from range", "[ucl]") +{ + using namespace nihil::ucl; + using namespace std::literals; + + auto vec = std::vector>{ + {"1"sv, integer(1)}, + {"42"sv, integer(42)}, + }; + + auto map = nihil::ucl::map(vec); + + REQUIRE(str(map.type()) == "object"); + REQUIRE(map["1"] == 1); + REQUIRE(map["42"] == 42); +} + +TEST_CASE("ucl: map: construct from iterator pair", "[ucl]") +{ + using namespace nihil::ucl; + using namespace std::literals; + + auto vec = std::vector>{ + {"1"sv, integer(1)}, + {"42"sv, integer(42)}, + }; + + auto map = nihil::ucl::map(std::ranges::begin(vec), + std::ranges::end(vec)); + + REQUIRE(str(map.type()) == "object"); + REQUIRE(map["1"] == 1); + REQUIRE(map["42"] == 42); +} + +TEST_CASE("ucl: map: insert", "[ucl]") +{ + using namespace nihil::ucl; + using namespace std::literals; + + auto m = map(); + + m.insert({"test1"sv, integer(42)}); + m.insert({"test2"sv, integer(666)}); + + REQUIRE(m["test1"] == 42); + REQUIRE(m["test2"] == 666); +} + +TEST_CASE("ucl: map: find", "[ucl]") +{ + using namespace nihil::ucl; + using namespace std::literals; + + auto map = nihil::ucl::map{ + {"1"sv, integer(1)}, + {"42"sv, integer(42)}, + }; + + auto obj = map.find("42"); + REQUIRE(obj.value() == 42); + + obj = map.find("43"); + REQUIRE(!obj.has_value()); +} + +TEST_CASE("ucl: map: iterate", "[ucl]") +{ + using namespace nihil::ucl; + using namespace std::literals; + + auto map = nihil::ucl::map{ + {"1"sv, integer(1)}, + {"42"sv, integer(42)}, + }; + + auto i = 0U; + + for (auto [key, value] : map) { + if (key == "1") + REQUIRE(value == 1); + else if (key == "42") + REQUIRE(value == 42); + else + REQUIRE(false); + ++i; + } + + REQUIRE(i == 2); +} + +TEST_CASE("ucl: map: operator[] throws key_not_found", "[ucl]") +{ + auto map = nihil::ucl::map(); + REQUIRE_THROWS_AS(map["nonesuch"], nihil::ucl::key_not_found); +} + +TEST_CASE("ucl: map: remove", "[uc]") +{ + using namespace std::literals; + using namespace nihil::ucl; + + auto map = nihil::ucl::map{ + {"1"sv, integer(1)}, + {"42"sv, integer(42)}, + }; + + REQUIRE(map.find("42") != std::nullopt); + REQUIRE(map.remove("42") == true); + REQUIRE(map.find("42") == std::nullopt); + REQUIRE(map["1"] == 1); + + REQUIRE(map.remove("42") == false); +} + +TEST_CASE("ucl: map: pop", "[uc]") +{ + using namespace std::literals; + using namespace nihil::ucl; + + auto map = nihil::ucl::map{ + {"1"sv, integer(1)}, + {"42"sv, integer(42)}, + }; + + REQUIRE(map.find("42") != std::nullopt); + + auto obj = map.pop("42"); + REQUIRE(obj.value() == 42); + + REQUIRE(!map.find("42")); + REQUIRE(map["1"] == 1); + + obj = map.pop("42"); + 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 -#include -#include - -#include - -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(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 -#include -#include +// A UCL object. The object is immutable and internally refcounted, so it +// may be copied as needed. #include 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(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(::ucl_object_type(uobj)) != type) + throw type_mismatch(type, static_cast(::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(::ucl_object_type(uobj)) != type) + throw type_mismatch(type, static_cast(::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/object.test.cc b/nihil.ucl/object.test.cc new file mode 100644 index 0000000..557653c --- /dev/null +++ b/nihil.ucl/object.test.cc @@ -0,0 +1,43 @@ +// This source code is released into the public domain. + +#include + +#include + +import nihil.std; +import nihil.ucl; + +TEST_CASE("ucl object: get_ucl_object", "[ucl]") +{ + auto obj = nihil::ucl::integer(42); + + REQUIRE(obj.get_ucl_object() != nullptr); + static_assert(std::same_as<::ucl_object_t *, + decltype(obj.get_ucl_object())>); + + auto const cobj = obj; + static_assert(std::same_as<::ucl_object_t const *, + decltype(cobj.get_ucl_object())>); +} + +TEST_CASE("ucl object: compare", "[ucl]") +{ + using namespace std::literals; + + auto obj_41 = nihil::ucl::parse("int = 41;"sv); + REQUIRE(obj_41); + + auto obj_42 = nihil::ucl::parse("int = 42;"sv); + REQUIRE(obj_42); + + auto obj_42_2 = nihil::ucl::parse("int = 42;"sv); + REQUIRE(obj_42_2); + + auto obj_43 = nihil::ucl::parse("int = 43;"sv); + REQUIRE(obj_43); + + REQUIRE(*obj_42 == *obj_42_2); + REQUIRE(*obj_42 != *obj_43); + REQUIRE(*obj_42 < *obj_43); + REQUIRE(*obj_42 > *obj_41); +} 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 -#include -#include - #include 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 @@ -80,7 +73,7 @@ struct convert_check> export template auto object_cast(object const &from) -> std::expected { - auto uobj = from.get_ucl_object(); + auto const *uobj = from.get_ucl_object(); co_await convert_check{}.check(uobj); co_return To(nihil::ucl::ref, uobj); diff --git a/nihil.ucl/parse.test.cc b/nihil.ucl/parse.test.cc new file mode 100644 index 0000000..79a722d --- /dev/null +++ b/nihil.ucl/parse.test.cc @@ -0,0 +1,55 @@ +// This source code is released into the public domain. + +#include +#include + +import nihil.std; +import nihil.ucl; + +namespace { +TEST_CASE("ucl parse: iterate array", "[ucl]") +{ + using namespace std::literals; + using namespace nihil::ucl; + + auto err = parse("value = [1, 42, 666];"sv); + REQUIRE(err); + + auto obj = *err; + + auto arr = obj["value"]; + REQUIRE(arr.key() == "value"); + + auto ints = object_cast>(arr); + REQUIRE(ints); + + auto vec = std::vector(std::from_range, *ints); + + REQUIRE(vec.size() == 3); + REQUIRE(vec[0] == 1); + REQUIRE(vec[1] == 42); + REQUIRE(vec[2] == 666); +} + +TEST_CASE("ucl parse: iterate hash", "[ucl]") +{ + using namespace std::literals; + using namespace nihil::ucl; + + auto input = "int = 42; bool = true; str = \"test\";"sv; + auto obj = parse(input); + REQUIRE(obj); + + for (auto &&[key, value] : *obj) { + REQUIRE(key == value.key()); + + if (key == "int") + REQUIRE(object_cast(value) == 42); + else if (key == "bool") + REQUIRE(object_cast(value) == true); + else if (key == "str") + REQUIRE(object_cast(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 -#include -#include - -#include - -module nihil.ucl; - -import nihil.error; - -namespace nihil::ucl { - -auto make_parser(int flags) -> std::expected -{ - 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(ud); - auto string = std::string_view( - reinterpret_cast(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 -{ - 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 -#include -#include -#include -#include -#include -#include - #include 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 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(ud); + auto string = std::string_view(reinterpret_cast(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 F> - auto register_macro(this parser &self, - std::string_view name, - F &&func) - -> void - requires (std::same_as>) + + template F> + auto register_macro(this parser &self, std::string_view name, F &&func) -> void + requires(std::same_as>) { - auto handler = std::make_unique( - std::forward(func)); + auto handler = std::make_unique(std::forward(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 - // Only bytes (chars) are permitted. + // Only bytes (chars) are permitted. requires(sizeof(std::ranges::range_value_t) == 1) { auto *p = self.get_parser(); - auto dptr = reinterpret_cast( - std::ranges::data(data)); + auto dptr = reinterpret_cast(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 - requires (!std::ranges::contiguous_range) + [[nodiscard]] auto + add(this parser &self, std::ranges::range auto &&data) -> std::expected + requires(!std::ranges::contiguous_range) { - auto cdata = std::vector( - std::from_range, - std::forward(data)); + auto cdata = std::vector(std::from_range, std::forward(data)); co_await self.add(std::move(cdata)); co_return {}; } // Return the top object of this parser. - [[nodiscard]] auto top(this parser &self) -> map; + [[nodiscard]] auto top(this parser &self) -> map + { + 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; +export [[nodiscard]] auto make_parser(int flags = 0) -> std::expected +{ + 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, error> +parse(int flags, std::ranges::range auto &&data) -> std::expected, error> { auto p = co_await make_parser(flags); co_await p.add(std::forward(data)); co_return p.top(); } -export [[nodiscard]] auto -parse(std::ranges::range auto &&data) - -> std::expected, error> +export [[nodiscard]] auto parse(std::ranges::range auto &&data) -> std::expected, error> { co_return co_await parse(0, std::forward(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 -#include -#include -#include -#include -#include - -#include - -module nihil.ucl; - -import nihil.error; - -namespace nihil::ucl { - -auto make_real(real::contained_type value) - -> std::expected -{ - 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( - ::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( - ::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 -#include -#include -#include - #include 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; +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 +{ + 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(std::numeric_limits::max()) || d < static_cast(std::numeric_limits::min())) @@ -80,32 +108,31 @@ export constexpr auto operator""_ucl (long double d) -> real return real(static_cast(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; - */ -export template<> +// std::formatter for a real. This provides the same format operations +// as std::formatter; +export template <> struct std::formatter { std::formatter base_formatter; - template - constexpr ParseContext::iterator parse(ParseContext& ctx) + template + constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator { return base_formatter.parse(ctx); } - template - FmtContext::iterator format(nihil::ucl::real const &o, - FmtContext& ctx) const + template + auto format(nihil::ucl::real const &o, FmtContext &ctx) const -> FmtContext::iterator { return base_formatter.format(o.value(), ctx); } diff --git a/nihil.ucl/real.test.cc b/nihil.ucl/real.test.cc new file mode 100644 index 0000000..e880d9a --- /dev/null +++ b/nihil.ucl/real.test.cc @@ -0,0 +1,245 @@ +// This source code is released into the public domain. + +#include +#include +#include + +import nihil.std; +import nihil.ucl; + +namespace { +TEST_CASE("ucl: real: invariants", "[ucl]") +{ + using namespace nihil::ucl; + + static_assert(std::same_as); + REQUIRE(real::ucl_type == object_type::real); + REQUIRE(static_cast<::ucl_type>(real::ucl_type) == UCL_FLOAT); + + static_assert(std::destructible); + static_assert(std::default_initializable); + static_assert(std::move_constructible); + static_assert(std::copy_constructible); + static_assert(std::equality_comparable); + static_assert(std::totally_ordered); + static_assert(std::swappable); +} + +TEST_CASE("ucl: real: constructor", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION ("default") { + auto r = real(); + REQUIRE(r == 0); + } + + SECTION ("with value") { + auto r = real(42.1); + REQUIRE_THAT(r.value(), Catch::Matchers::WithinRel(42.1)); + } +} + +TEST_CASE("ucl: real: literal", "[ucl]") +{ + SECTION ("with namespace nihil::ucl::literals") { + using namespace nihil::ucl::literals; + + auto r = 42.5_ucl; + REQUIRE(r.type() == nihil::ucl::object_type::real); + REQUIRE_THAT(r.value(), Catch::Matchers::WithinRel(42.5)); + } + + SECTION ("with namespace nihil::literals") { + using namespace nihil::literals; + + auto r = 42.5_ucl; + REQUIRE(r.type() == nihil::ucl::object_type::real); + REQUIRE_THAT(r.value(), Catch::Matchers::WithinRel(42.5)); + } +} + +TEST_CASE("ucl: real: construct from UCL object", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION ("ref, correct type") { + auto *uobj = ::ucl_object_fromdouble(42); + + auto r = real(ref, uobj); + REQUIRE(r == 42); + + ::ucl_object_unref(uobj); + } + + 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); + + REQUIRE_THROWS_AS(real(ref, uobj), type_mismatch); + + ::ucl_object_unref(uobj); + } + + SECTION ("noref, wrong type") { + auto *uobj = ::ucl_object_fromint(42); + + REQUIRE_THROWS_AS(real(noref, uobj), type_mismatch); + + ::ucl_object_unref(uobj); + } +} + +TEST_CASE("ucl: real: make_real", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION ("default value") { + auto i = make_real().value(); + REQUIRE(i == 0); + } + + SECTION ("explicit value") { + auto i = make_real(42).value(); + REQUIRE(i == 42); + } +} + +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); + + swap(r1, r2); + + REQUIRE(r1 == 2.); + REQUIRE(r2 == 1.); +} + +TEST_CASE("ucl: real: value()", "[ucl]") +{ + using namespace nihil::ucl; + + auto r = 42.5_ucl; + REQUIRE_THAT(r.value(), Catch::Matchers::WithinRel(42.5)); +} + +TEST_CASE("ucl: real: key()", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION ("parsed with key") { + auto obj = parse("a_real = 42.5").value(); + auto r = object_cast(obj["a_real"]).value(); + REQUIRE(r.key() == "a_real"); + } + + SECTION ("bare real, no key") { + auto i = 42.5_ucl; + REQUIRE(i.key().empty() == true); + } +} + +TEST_CASE("ucl: real: comparison", "[ucl]") +{ + using namespace nihil::ucl; + + auto i = nihil::ucl::real(42.5); + + SECTION ("operator==") { + REQUIRE(i == 42.5); + REQUIRE(i == 42.5_ucl); + } + + SECTION ("operator!=") { + REQUIRE(i != 1); + REQUIRE(i != 1._ucl); + } + + SECTION ("operator<") { + REQUIRE(i < 43); + REQUIRE(i < 43._ucl); + } + + SECTION ("operator>") { + REQUIRE(i > 1); + REQUIRE(i > 1._ucl); + } +} + +TEST_CASE("ucl: real: parse", "[ucl]") +{ + using namespace nihil::ucl; + + auto obj = parse("value = 42.1").value(); + + auto v = obj["value"]; + REQUIRE(v.key() == "value"); + REQUIRE_THAT(object_cast(v).value().value(), Catch::Matchers::WithinRel(42.1)); +} + +TEST_CASE("ucl: real: parse and emit", "[ucl]") +{ + using namespace nihil::ucl; + + auto ucl = parse("real = 42.2").value(); + + auto output = std::string(); + emit(ucl, emitter::configuration, std::back_inserter(output)); + + REQUIRE(output == "real = 42.2;\n"); +} + +TEST_CASE("ucl: real: format", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION ("bare real") { + auto str = std::format("{}", 42.5_ucl); + REQUIRE(str == "42.5"); + } + + SECTION ("parsed real") { + auto obj = parse("real = 42.5;").value(); + auto r = object_cast(obj["real"]).value(); + + auto str = std::format("{}", r); + REQUIRE(str == "42.5"); + } + + SECTION ("with format string") { + auto str = std::format("{:10.5f}", 42.5_ucl); + REQUIRE(str == " 42.50000"); + } +} + +TEST_CASE("ucl: real: print to ostream", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION ("bare real") { + auto strm = std::ostringstream(); + strm << 42.5_ucl; + + REQUIRE(strm.str() == "42.5"); + } + + SECTION ("parsed real") { + auto obj = parse("real = 42.5;").value(); + auto i = object_cast(obj["real"]).value(); + + auto strm = std::ostringstream(); + strm << i; + + 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 -#include -#include -#include -#include - -#include - -module nihil.ucl; - -import nihil.error; - -namespace nihil::ucl { - -auto make_string() -> std::expected -{ - return make_string(std::string_view("")); -} - -auto make_string(char const *s) -> std::expected -{ - return make_string(std::string_view(s)); -} - -auto make_string(std::string_view s) -> std::expected -{ - 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( - ::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( - ::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 -#include -#include -#include -#include - #include 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,158 +25,235 @@ 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 - requires (!std::same_as && - std::same_as>) - 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 - requires (!std::ranges::contiguous_range && - std::same_as>) - 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 - requires (std::same_as>) + // 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 + requires(!std::same_as && + std::same_as>) + 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 + requires(!std::ranges::contiguous_range && + std::same_as>) + explicit string(Range range) + : string(std::string(std::from_range, std::forward(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 + requires(std::same_as>) 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; +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; +export [[nodiscard]] auto make_string(std::string_view s) -> std::expected +{ + 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 +{ + return make_string(std::string_view("")); +} // From C literal -export [[nodiscard]] auto -make_string(char const *) -> std::expected; +export [[nodiscard]] auto make_string(char const *s) -> std::expected +{ + return make_string(std::string_view(s)); +} // From contiguous range -export template -requires (!std::same_as && - std::same_as>) +export template +requires(!std::same_as && + std::same_as>) [[nodiscard]] auto make_string(Range &&range) { - return make_string(std::string_view(range)); + return make_string(std::string_view(std::forward(range))); } // From non-contiguous range -export template -requires (!std::ranges::contiguous_range && - std::same_as>) +export template +requires(!std::ranges::contiguous_range && + std::same_as>) [[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))); } // From iterator pair -export template -requires (std::same_as>) +export template +requires(std::same_as>) [[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. */ @@ -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. */ -export template<> +export template <> struct std::formatter { std::formatter base_formatter; - template - constexpr ParseContext::iterator parse(ParseContext& ctx) + template + constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator { return base_formatter.parse(ctx); } - template - FmtContext::iterator format(nihil::ucl::string const &o, - FmtContext& ctx) const + template + auto format(nihil::ucl::string const &o, FmtContext &ctx) const -> FmtContext::iterator { return base_formatter.format(o.value(), ctx); } diff --git a/nihil.ucl/string.test.cc b/nihil.ucl/string.test.cc new file mode 100644 index 0000000..68c57e8 --- /dev/null +++ b/nihil.ucl/string.test.cc @@ -0,0 +1,407 @@ +// This source code is released into the public domain. + +#include +#include + +import nihil.std; +import nihil.ucl; + +namespace { +TEST_CASE("ucl: string: invariants", "[ucl]") +{ + using namespace nihil::ucl; + + static_assert(std::same_as); + REQUIRE(string::ucl_type == object_type::string); + REQUIRE(static_cast<::ucl_type>(string::ucl_type) == UCL_STRING); + + static_assert(std::destructible); + static_assert(std::default_initializable); + static_assert(std::move_constructible); + static_assert(std::copy_constructible); + static_assert(std::equality_comparable); + static_assert(std::totally_ordered); + static_assert(std::swappable); + + static_assert(std::ranges::contiguous_range); + static_assert(std::same_as>); +} + +TEST_CASE("ucl: string: literal", "[ucl]") +{ + SECTION("with namespace nihil::ucl::literals") { + using namespace nihil::ucl::literals; + + auto s = "testing"_ucl; + REQUIRE(s.type() == nihil::ucl::object_type::string); + REQUIRE(s == "testing"); + } + + SECTION("with namespace nihil::literals") { + using namespace nihil::literals; + + auto s = "testing"_ucl; + REQUIRE(s.type() == nihil::ucl::object_type::string); + REQUIRE(s == "testing"); + } +} + +TEST_CASE("ucl: string: construct", "[ucl]") +{ + using namespace nihil::ucl; + using namespace std::literals; + + SECTION("empty string") { + auto str = string(); + REQUIRE(str.type() == object_type::string); + REQUIRE(str == ""); + } + + SECTION("with integer-like value") { + auto str = "42"_ucl; + REQUIRE(str.type() == object_type::string); + REQUIRE(str == "42"); + } + + SECTION("with boolean-like value") { + auto str = "true"_ucl; + REQUIRE(str.type() == object_type::string); + REQUIRE(str == "true"); + } + + SECTION("from string literal") { + auto str = string("testing"); + REQUIRE(str.type() == object_type::string); + REQUIRE(str == "testing"); + } + + SECTION("from std::string") { + auto str = string("testing"s); + REQUIRE(str.type() == object_type::string); + REQUIRE(str == "testing"); + } + + SECTION("from std::string_view") { + auto str = string("testing"sv); + 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 = string(s); + 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 = string(s); + 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 = string(s.begin(), s.end()); + 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 = string(s.begin(), s.end()); + REQUIRE(str.type() == object_type::string); + REQUIRE(str == "testing"); + } +} + +TEST_CASE("ucl: string: construct from UCL object", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION("ref, correct type") { + auto *uobj = ::ucl_object_fromstring("testing"); + + auto const s = string(ref, uobj); + REQUIRE(s == "testing"); + + ::ucl_object_unref(uobj); + } + + SECTION("noref, correct type") { + auto *uobj = ::ucl_object_fromstring("testing"); + + auto const s = string(noref, uobj); + REQUIRE(s == "testing"); + } + + SECTION("ref, wrong type") { + auto *uobj = ::ucl_object_frombool(true); + + REQUIRE_THROWS_AS(string(ref, uobj), type_mismatch); + + ::ucl_object_unref(uobj); + } + + SECTION("noref, wrong type") { + auto *uobj = ::ucl_object_frombool(true); + + REQUIRE_THROWS_AS(string(noref, uobj), type_mismatch); + + ::ucl_object_unref(uobj); + } +} + +TEST_CASE("ucl: string: make_string", "[ucl]") +{ + using namespace nihil::ucl; + using namespace std::literals; + + SECTION("empty string") { + auto const str = make_string().value(); + REQUIRE(str.type() == object_type::string); + REQUIRE(str == ""); + } + + SECTION("from string literal") { + auto const str = make_string("testing").value(); + REQUIRE(str.type() == object_type::string); + REQUIRE(str == "testing"); + } + + SECTION("from std::string") { + auto const str = make_string("testing"s).value(); + REQUIRE(str.type() == object_type::string); + REQUIRE(str == "testing"); + } + + SECTION("from std::string_view") { + auto const str = make_string("testing"sv).value(); + REQUIRE(str.type() == object_type::string); + REQUIRE(str == "testing"); + } + + SECTION("from contiguous range") { + 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 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 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 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"); + } +} + +TEST_CASE("ucl: string: swap", "[ucl]") +{ + // do not add using namespace nihil::ucl + + auto s1 = nihil::ucl::string("one"); + auto s2 = nihil::ucl::string("two"); + + swap(s1, s2); + + REQUIRE(s1 == "two"); + REQUIRE(s2 == "one"); +} + +TEST_CASE("ucl: string: value()", "[ucl]") +{ + using namespace nihil::ucl; + + auto const s = string(R"(te"st)"); + REQUIRE(s.value() == R"(te"st)"); +} + +TEST_CASE("ucl: string: key()", "[ucl]") +{ + using namespace nihil::ucl; + + auto obj = parse(R"(a_string = "test")").value(); + REQUIRE(object_cast(obj["a_string"]).value().key() == "a_string"); + + auto const s = string("test"); + REQUIRE(s.key().empty() == true); +} + +TEST_CASE("ucl: string: size", "[ucl]") +{ + using namespace nihil::ucl; + + REQUIRE(string().size() == 0); + REQUIRE(string("test").size() == 4); +} + +TEST_CASE("ucl: string: empty", "[ucl]") +{ + using namespace nihil::ucl; + + REQUIRE(string().empty() == true); + REQUIRE(string("test").empty() == false); +} + +TEST_CASE("ucl: string: iterate", "[ucl]") +{ + using namespace nihil::ucl; + + auto str = "test"_ucl; + + SECTION("as iterator pair") { + auto begin = str.begin(); + static_assert(std::contiguous_iterator); + + auto end = str.end(); + static_assert(std::sentinel_for); + + REQUIRE(*begin == 't'); + ++begin; + REQUIRE(*begin == 'e'); + ++begin; + REQUIRE(*begin == 's'); + ++begin; + REQUIRE(*begin == 't'); + ++begin; + + REQUIRE(begin == end); + } + + SECTION("as range") { + auto s = std::string(std::from_range, str); + REQUIRE(s == "test"); + } +} + +TEST_CASE("ucl: string: comparison", "[ucl]") +{ + using namespace nihil::ucl; + + auto str = "testing"_ucl; + + SECTION("operator==") { + REQUIRE(str == "testing"_ucl); + REQUIRE(str == std::string_view("testing")); + REQUIRE(str == std::string("testing")); + REQUIRE(str == "testing"); + } + + SECTION("operator!=") { + REQUIRE(str != "test"_ucl); + REQUIRE(str != std::string_view("test")); + REQUIRE(str != std::string("test")); + REQUIRE(str != "test"); + } + + SECTION("operator<") { + REQUIRE(str < "zzz"_ucl); + REQUIRE(str < std::string_view("zzz")); + REQUIRE(str < std::string("zzz")); + REQUIRE(str < "zzz"); + } + + SECTION("operator>") { + REQUIRE(str > "aaa"_ucl); + REQUIRE(str > std::string_view("aaa")); + REQUIRE(str > std::string("aaa")); + REQUIRE(str > "aaa"); + } +} + +TEST_CASE("ucl: string: parse", "[ucl]") +{ + using namespace nihil::ucl; + + auto obj = parse(R"(value = "te\"st")").value(); + + auto v = obj["value"]; + REQUIRE(v.key() == "value"); + REQUIRE(object_cast(v).value() == "te\"st"); +} + +TEST_CASE("ucl: string: emit", "[ucl]") +{ + using namespace nihil::ucl; + + auto ucl = parse(R"(str = "te\"st";)").value(); + + auto output = std::string(); + emit(ucl, emitter::configuration, std::back_inserter(output)); + + REQUIRE(output == "str = \"te\\\"st\";\n"); +} + +TEST_CASE("ucl: string: format", "[ucl]") +{ + using namespace nihil::ucl; + using namespace std::literals; + + auto constexpr test_string = "te\"st"sv; + + SECTION("bare string") { + auto str = std::format("{}", string(test_string)); + REQUIRE(str == test_string); + } + + SECTION("parsed string") { + auto obj = parse(R"(string = "te\"st";)").value(); + auto s = object_cast(obj["string"]).value(); + + auto str = std::format("{}", s); + REQUIRE(str == test_string); + } + + SECTION("with format string") { + auto str = std::format("{: >10}", string(test_string)); + REQUIRE(str == " te\"st"); + } +} + +TEST_CASE("ucl: string: print to ostream", "[ucl]") +{ + using namespace nihil::ucl; + using namespace std::literals; + + auto constexpr test_string = "te\"st"sv; + + SECTION("bare string") { + auto strm = std::ostringstream(); + strm << string(test_string); + + REQUIRE(strm.str() == test_string); + } + + SECTION("parsed string") { + auto obj = parse(R"(string = "te\"st";)").value(); + auto s = object_cast(obj["string"]).value(); + + auto strm = std::ostringstream(); + strm << s; + + REQUIRE(strm.str() == test_string); + } + + SECTION("with format string") { + auto str = std::format("{: >10}", string(test_string)); + 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/tests/array.cc b/nihil.ucl/tests/array.cc deleted file mode 100644 index 866fa45..0000000 --- a/nihil.ucl/tests/array.cc +++ /dev/null @@ -1,478 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -#include -#include -#include -#include -#include - -#include -#include - -import nihil.ucl; - -TEST_CASE("ucl: array: invariants", "[ucl]") -{ - using namespace nihil::ucl; - - REQUIRE(array<>::ucl_type == object_type::array); - REQUIRE(static_cast<::ucl_type>(array<>::ucl_type) == UCL_ARRAY); - - static_assert(std::destructible>); - static_assert(std::default_initializable>); - static_assert(std::move_constructible>); - static_assert(std::copy_constructible>); - static_assert(std::equality_comparable>); - static_assert(std::totally_ordered>); - static_assert(std::swappable>); - - static_assert(std::ranges::sized_range>); - static_assert(std::same_as>, - integer>); -} - -TEST_CASE("ucl: array: constructor", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("default") { - auto arr = array(); - REQUIRE(arr.size() == 0); - REQUIRE(str(arr.type()) == "array"); - } - - SECTION("from range") { - auto vec = std::vector{integer(1), integer(42)}; - auto arr = array(std::from_range, vec); - - REQUIRE(arr.size() == 2); - REQUIRE(arr[0] == 1); - REQUIRE(arr[1] == 42); - } - - SECTION("from iterator pair") { - auto vec = std::vector{integer(1), integer(42)}; - auto arr = array(std::ranges::begin(vec), - std::ranges::end(vec)); - - REQUIRE(arr.size() == 2); - REQUIRE(arr[0] == 1); - REQUIRE(arr[1] == 42); - } - - SECTION("from initializer_list") { - auto arr = array{integer(1), integer(42)}; - - REQUIRE(arr.size() == 2); - REQUIRE(arr[0] == 1); - REQUIRE(arr[1] == 42); - } -} - -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); - ::ucl_array_append(uarr, uint); - - auto arr = array(ref, uarr); - REQUIRE(arr[0] == 42); - - ::ucl_object_unref(uarr); - } - - 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(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); - ::ucl_array_append(uarr, uint); - - auto arr = array(noref, uarr); - REQUIRE_THROWS_AS(arr[0], type_mismatch); - } - - 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); - - REQUIRE_THROWS_AS(array(noref, uobj), type_mismatch); - - ::ucl_object_unref(uobj); - } -} - -TEST_CASE("ucl: array: swap", "[ucl]") -{ - // do not add using namespace nihil::ucl - - auto arr1 = nihil::ucl::array{ - nihil::ucl::integer(1), - nihil::ucl::integer(2) - }; - - auto arr2 = nihil::ucl::array{ - nihil::ucl::integer(3), - }; - - swap(arr1, arr2); - - REQUIRE(arr1.size() == 1); - REQUIRE(arr1[0] == 3); - - REQUIRE(arr2.size() == 2); - REQUIRE(arr2[0] == 1); -} - -TEST_CASE("ucl: array: push_back", "[ucl]") -{ - using namespace nihil::ucl; - - auto arr = array(); - REQUIRE(arr.size() == 0); - - arr.push_back(integer(1)); - arr.push_back(integer(42)); - arr.push_back(integer(666)); - - REQUIRE(arr.size() == 3); - REQUIRE(arr[0] == 1); - REQUIRE(arr[1] == 42); - REQUIRE(arr[2] == 666); - - REQUIRE_THROWS_AS(arr[3], std::out_of_range); - - REQUIRE(arr.front() == 1); - REQUIRE(arr.back() == 666); -} - -TEST_CASE("ucl: array: compare", "[ucl]") -{ - using namespace nihil::ucl; - - auto arr = array{ - integer(1), integer(42), integer(666) - }; - - auto arr2 = array(); - REQUIRE(arr != arr2); - - arr2.push_back(integer(1)); - arr2.push_back(integer(42)); - arr2.push_back(integer(666)); - REQUIRE(arr == arr2); - - auto arr3 = array{ - integer(1), integer(1), integer(1) - }; - - REQUIRE(arr != arr3); -} - -TEST_CASE("ucl: array: iterator", "[ucl]") -{ - using namespace nihil::ucl; - - auto arr = array{integer(1), integer(42), integer(666)}; - - auto it = arr.begin(); - REQUIRE(*it == 1); - auto end = arr.end(); - REQUIRE(it != end); - REQUIRE(it < end); - - ++it; - REQUIRE(*it == 42); - - ++it; - REQUIRE(*it == 666); - - --it; - REQUIRE(*it == 42); - - ++it; - REQUIRE(it != end); - ++it; - REQUIRE(it == end); -} - -TEST_CASE("ucl: array: parse", "[ucl]") -{ - using namespace std::literals; - using namespace nihil::ucl; - - auto obj = parse("value = [1, 42, 666]"sv).value(); - - auto arr = object_cast>(obj["value"]).value(); - - REQUIRE(arr.size() == 3); - REQUIRE(arr[0] == 1); - REQUIRE(arr[1] == 42); - REQUIRE(arr[2] == 666); -} - -TEST_CASE("ucl: array: emit", "[ucl]") -{ - using namespace nihil::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"); -} - -TEST_CASE("ucl: array: format", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("empty array") { - auto arr = array(); - REQUIRE(std::format("{}", arr) == "[]"); - } - - SECTION("bare array") { - auto arr = array{ - integer(1), integer(42), integer(666) - }; - - auto output = std::format("{}", arr); - REQUIRE(output == "[1, 42, 666]"); - } - - SECTION("parsed array") { - auto ucl = parse("array = [1, 42, 666];").value(); - auto arr = object_cast>(ucl["array"]).value(); - - auto output = std::format("{}", arr); - REQUIRE(output == "[1, 42, 666]"); - } -} - -TEST_CASE("ucl: array: print to ostream", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("empty array") { - auto arr = array(); - auto strm = std::ostringstream(); - strm << arr; - - REQUIRE(strm.str() == "[]"); - } - - SECTION("bare array") { - auto arr = array{ - integer(1), integer(42), integer(666) - }; - auto strm = std::ostringstream(); - strm << arr; - - REQUIRE(strm.str() == "[1, 42, 666]"); - } - - SECTION("parsed array") { - auto ucl = parse("array = [1, 42, 666];").value(); - auto arr = object_cast>(ucl["array"]).value(); - auto strm = std::ostringstream(); - strm << arr; - - REQUIRE(strm.str() == "[1, 42, 666]"); - } -} - -TEST_CASE("ucl: array is a sized_range", "[ucl]") -{ - using namespace nihil::ucl; - - auto arr = array{integer(1), integer(42), integer(666)}; - - auto size = std::ranges::size(arr); - REQUIRE(size == 3); - - auto begin = std::ranges::begin(arr); - static_assert(std::random_access_iterator); - - auto end = std::ranges::end(arr); - static_assert(std::sentinel_for); - - REQUIRE(std::distance(begin, end) == 3); - - auto vec = std::vector(); - 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 int_vec = std::vector(); - std::ranges::copy(arr_as_ints, std::back_inserter(int_vec)); - REQUIRE(int_vec == std::vector{1, 42, 666}); - -} - -TEST_CASE("ucl: array: bad object_cast", "[ucl]") -{ - using namespace nihil::ucl; - - auto arr = array(); - - auto cast_ok = object_cast(arr); - REQUIRE(!cast_ok); -} - -TEST_CASE("ucl: array: heterogeneous elements", "[ucl]") -{ - using namespace std::literals; - using namespace nihil::ucl; - - auto obj_err = parse("array [ 42, true, \"test\" ];"); - REQUIRE(obj_err); - auto obj = *obj_err; - - auto err = object_cast>(obj["array"]); - REQUIRE(err); - - auto arr = *err; - REQUIRE(arr.size() == 3); - - auto int_obj = object_cast(arr[0]); - REQUIRE(int_obj); - REQUIRE(*int_obj == 42); - - auto bool_obj = object_cast(arr[1]); - REQUIRE(bool_obj); - REQUIRE(*bool_obj == true); - - auto string_obj = object_cast(arr[2]); - REQUIRE(string_obj); - REQUIRE(*string_obj == "test"); -} - -TEST_CASE("ucl: array: heterogenous cast", "[ucl]") -{ - using namespace nihil::ucl; - - auto arr = array<>(); - arr.push_back(integer(42)); - arr.push_back(boolean(true)); - - // Converting to an array should fail. - auto cast_ok = object_cast>(arr); - REQUIRE(!cast_ok); - - // Converting to array should succeed. - auto err = object_cast>(arr); - REQUIRE(err); - - auto obj_arr = *err; - REQUIRE(obj_arr[0] == integer(42)); -} - -TEST_CASE("ucl: array: homogeneous cast", "[ucl]") -{ - using namespace nihil::ucl; - - auto arr = array<>(); - arr.push_back(integer(1)); - arr.push_back(integer(42)); - - auto obj = object(ref, arr.get_ucl_object()); - - // Converting to array should fail. - auto cast_ok = object_cast>(obj); - REQUIRE(!cast_ok); - - // Converting to an array should succeed. - auto err = object_cast>(obj); - REQUIRE(err); - - auto obj_arr = *err; - REQUIRE(obj_arr[0] == 1); - REQUIRE(obj_arr[1] == 42); -} - -TEST_CASE("array iterator: empty iterator", "[ucl]") -{ - using namespace nihil::ucl; - - auto it = array_iterator(); - - REQUIRE_THROWS_AS(*it, std::logic_error); - REQUIRE_THROWS_AS(it[0], std::logic_error); - REQUIRE_THROWS_AS(it++, std::logic_error); - REQUIRE_THROWS_AS(++it, std::logic_error); - - auto it2 = array_iterator(); - REQUIRE(it == it2); - REQUIRE((it < it2) == false); - REQUIRE((it > it2) == false); -} - -TEST_CASE("array iterator: invalid operations", "[ucl]") -{ - using namespace nihil::ucl; - - auto arr = array{ integer(42) }; - auto it = arr.begin(); - - 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") { - ++it; - REQUIRE(it == arr.end()); - - REQUIRE_THROWS_AS(++it, std::logic_error); - REQUIRE_THROWS_AS(it++, std::logic_error); - REQUIRE_THROWS_AS(it + 1, std::logic_error); - } - - SECTION("dereference iterator at end") { - REQUIRE_THROWS_AS(it[1], std::logic_error); - - ++it; - REQUIRE(it == arr.end()); - - REQUIRE_THROWS_AS(*it, std::logic_error); - } - - SECTION("compare with different array") { - auto arr2 = array{ 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") { - auto it2 = array_iterator(); - REQUIRE_THROWS_AS(it == it2, std::logic_error); - REQUIRE_THROWS_AS(it > it2, std::logic_error); - REQUIRE_THROWS_AS(it - it2, std::logic_error); - } -} diff --git a/nihil.ucl/tests/boolean.cc b/nihil.ucl/tests/boolean.cc deleted file mode 100644 index f7ef95e..0000000 --- a/nihil.ucl/tests/boolean.cc +++ /dev/null @@ -1,224 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -#include -#include - -#include -#include - -import nihil.ucl; - -TEST_CASE("ucl: boolean: invariants", "[ucl]") -{ - using namespace nihil::ucl; - - static_assert(std::same_as); - REQUIRE(boolean::ucl_type == object_type::boolean); - REQUIRE(static_cast<::ucl_type>(boolean::ucl_type) == UCL_BOOLEAN); - - static_assert(std::destructible); - static_assert(std::default_initializable); - static_assert(std::move_constructible); - static_assert(std::copy_constructible); - static_assert(std::equality_comparable); - static_assert(std::totally_ordered); - static_assert(std::swappable); -} - -TEST_CASE("ucl: boolean: constructor", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("default") { - auto b = boolean(); - REQUIRE(b == false); - } - - SECTION("with value") { - auto b = boolean(true); - REQUIRE(b == true); - } -} - -TEST_CASE("ucl: boolean: construct from UCL object", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("ref, correct type") { - auto uobj = ::ucl_object_frombool(true); - - auto i = boolean(ref, uobj); - REQUIRE(i == true); - - ::ucl_object_unref(uobj); - } - - 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); - - REQUIRE_THROWS_AS(boolean(ref, uobj), type_mismatch); - - ::ucl_object_unref(uobj); - } - - SECTION("noref, wrong type") { - auto uobj = ::ucl_object_fromint(1); - - REQUIRE_THROWS_AS(boolean(noref, uobj), type_mismatch); - - ::ucl_object_unref(uobj); - } -} - -TEST_CASE("ucl: boolean: make_boolean", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("default value") { - auto b = make_boolean().value(); - REQUIRE(b == false); - } - - SECTION("explicit value") { - auto b = make_boolean(true).value(); - REQUIRE(b == true); - } -} - -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); - - swap(b1, b2); - - REQUIRE(b1 == false); - REQUIRE(b2 == true); -} - -TEST_CASE("ucl: boolean: value()", "[ucl]") -{ - auto b = nihil::ucl::boolean(true); - REQUIRE(b.value() == true); -} - -TEST_CASE("ucl: boolean: key()", "[ucl]") -{ - using namespace nihil::ucl; - - auto err = parse("a_bool = true"); - REQUIRE(err); - - auto obj = *err; - REQUIRE(object_cast(obj["a_bool"])->key() == "a_bool"); - - auto b = nihil::ucl::boolean(true); - REQUIRE(b.key() == ""); -} - -TEST_CASE("ucl: boolean: comparison", "[ucl]") -{ - using namespace nihil::ucl; - - auto b = boolean(true); - - SECTION("operator==") { - REQUIRE(b == true); - REQUIRE(b == boolean(true)); - } - - SECTION("operator!=") { - REQUIRE(b != false); - REQUIRE(b != boolean(false)); - } - - SECTION("operator<") { - REQUIRE(b <= true); - REQUIRE(b <= nihil::ucl::boolean(true)); - } - - SECTION("operator>") { - REQUIRE(b > false); - REQUIRE(b > nihil::ucl::boolean(false)); - } -} - -TEST_CASE("ucl: boolean: parse", "[ucl]") -{ - using namespace nihil::ucl; - - auto obj = parse("value = true").value(); - - auto v = obj["value"]; - REQUIRE(v.key() == "value"); - REQUIRE(object_cast(v).value() == true); -} - -TEST_CASE("ucl: boolean: parse and emit", "[ucl]") -{ - using namespace nihil::ucl; - - auto ucl = parse("bool = true;").value(); - - auto output = std::string(); - emit(ucl, nihil::ucl::emitter::configuration, - std::back_inserter(output)); - - REQUIRE(output == "bool = true;\n"); -} - -TEST_CASE("ucl: boolean: format", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("bare boolean") { - auto str = std::format("{}", boolean(true)); - REQUIRE(str == "true"); - } - - SECTION("parsed boolean") { - auto obj = parse("bool = true;").value(); - auto b = object_cast(obj["bool"]).value(); - - auto str = std::format("{}", b); - REQUIRE(str == "true"); - } - - SECTION("with format string") { - auto str = std::format("{: >5}", boolean(true)); - REQUIRE(str == " true"); - } -} - -TEST_CASE("ucl: boolean: print to ostream", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("bare boolean") { - auto strm = std::ostringstream(); - strm << boolean(true); - - REQUIRE(strm.str() == "true"); - } - - SECTION("parsed boolean") { - auto obj = parse("bool = true;").value(); - auto i = object_cast(obj["bool"]).value(); - - auto strm = std::ostringstream(); - strm << i; - - REQUIRE(strm.str() == "true"); - } -} diff --git a/nihil.ucl/tests/emit.cc b/nihil.ucl/tests/emit.cc deleted file mode 100644 index a7dcd71..0000000 --- a/nihil.ucl/tests/emit.cc +++ /dev/null @@ -1,93 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -#include -#include - -#include - -import nihil.ucl; - -TEST_CASE("ucl: emit to std::ostream", "[ucl]") -{ - using namespace std::literals; - - auto obj = nihil::ucl::parse("int = [1, 42, 666]"sv); - REQUIRE(obj); - - auto strm = std::ostringstream(); - strm << *obj; - - // The ostream emitter produces JSON. - REQUIRE(strm.str() == std::format("{:j}", *obj)); -} - -TEST_CASE("ucl: emit JSON with std::format", "[ucl]") -{ - using namespace std::literals; - - auto obj = nihil::ucl::parse("int = [1, 42, 666]"sv); - REQUIRE(obj); - - auto str = std::format("{:j}", *obj); - - REQUIRE(str == -"{\n" -" \"int\": [\n" -" 1,\n" -" 42,\n" -" 666\n" -" ]\n" -"}"); - - // Make sure JSON is the default format. - auto str2 = std::format("{}", *obj); - REQUIRE(str == str2); -} - -TEST_CASE("ucl: emit compact JSON with std::format", "[ucl]") -{ - using namespace std::literals; - - auto obj = nihil::ucl::parse("int = [1, 42, 666]"sv); - REQUIRE(obj); - - auto str = std::format("{:J}", *obj); - - REQUIRE(str == "{\"int\":[1,42,666]}"); -} - -TEST_CASE("ucl: emit configuration with std::format", "[ucl]") -{ - using namespace std::literals; - - auto obj = nihil::ucl::parse("int = [1, 42, 666]"sv); - REQUIRE(obj); - - auto str = std::format("{:c}", *obj); - - REQUIRE(str == -"int [\n" -" 1,\n" -" 42,\n" -" 666,\n" -"]\n"); -} - -TEST_CASE("ucl: emit YAML with std::format", "[ucl]") -{ - using namespace std::literals; - - auto obj = nihil::ucl::parse("int = [1, 42, 666]"sv); - REQUIRE(obj); - - auto str = std::format("{:y}", *obj); - - REQUIRE(str == -"int: [\n" -" 1,\n" -" 42,\n" -" 666\n" -"]"); -} diff --git a/nihil.ucl/tests/integer.cc b/nihil.ucl/tests/integer.cc deleted file mode 100644 index 6584764..0000000 --- a/nihil.ucl/tests/integer.cc +++ /dev/null @@ -1,247 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -#include -#include -#include - -#include -#include - -import nihil.ucl; - -TEST_CASE("ucl: integer: invariants", "[ucl]") -{ - using namespace nihil::ucl; - - static_assert(std::same_as); - REQUIRE(integer::ucl_type == object_type::integer); - REQUIRE(static_cast<::ucl_type>(integer::ucl_type) == UCL_INT); - - static_assert(std::destructible); - static_assert(std::default_initializable); - static_assert(std::move_constructible); - static_assert(std::copy_constructible); - static_assert(std::equality_comparable); - static_assert(std::totally_ordered); - static_assert(std::swappable); -} - -TEST_CASE("ucl: integer: constructor", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("default") { - auto i = integer(); - REQUIRE(i == 0); - } - - SECTION("with value") { - auto i = integer(42); - REQUIRE(i == 42); - } -} - -TEST_CASE("ucl: integer: literal", "[ucl]") -{ - SECTION("with namespace nihil::ucl::literals") { - using namespace nihil::ucl::literals; - - auto i = 42_ucl; - REQUIRE(i.type() == nihil::ucl::object_type::integer); - REQUIRE(i == 42); - } - - SECTION("with namespace nihil::literals") { - using namespace nihil::literals; - - auto i = 42_ucl; - REQUIRE(i.type() == nihil::ucl::object_type::integer); - REQUIRE(i == 42); - } -} - -TEST_CASE("ucl: integer: construct from UCL object", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("ref, correct type") { - auto uobj = ::ucl_object_fromint(42); - - auto i = integer(ref, uobj); - REQUIRE(i == 42); - - ::ucl_object_unref(uobj); - } - - SECTION("noref, correct type") { - auto uobj = ::ucl_object_fromint(42); - - auto i = integer(noref, uobj); - REQUIRE(i == 42); - } - - SECTION("ref, wrong type") { - auto uobj = ::ucl_object_frombool(true); - - REQUIRE_THROWS_AS(integer(ref, uobj), type_mismatch); - - ::ucl_object_unref(uobj); - } - - SECTION("noref, wrong type") { - auto uobj = ::ucl_object_frombool(true); - - REQUIRE_THROWS_AS(integer(noref, uobj), type_mismatch); - - ::ucl_object_unref(uobj); - } -} - -TEST_CASE("ucl: integer: make_integer", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("default value") { - auto i = make_integer().value(); - REQUIRE(i == 0); - } - - SECTION("explicit value") { - auto i = make_integer(42).value(); - REQUIRE(i == 42); - } -} - -TEST_CASE("ucl: integer: swap", "[ucl]") -{ - // do not add using namespace nihil::ucl - - auto i1 = nihil::ucl::integer(1); - auto i2 = nihil::ucl::integer(2); - - swap(i1, i2); - - REQUIRE(i1 == 2); - REQUIRE(i2 == 1); -} - -TEST_CASE("ucl: integer: value()", "[ucl]") -{ - using namespace nihil::ucl; - - auto i = 42_ucl; - REQUIRE(i.value() == 42); -} - -TEST_CASE("ucl: integer: key()", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("parsed with key") { - auto obj = parse("an_int = 42").value(); - auto i = object_cast(obj["an_int"]).value(); - REQUIRE(i.key() == "an_int"); - } - - SECTION("bare integer, no key") { - auto i = 42_ucl; - REQUIRE(i.key() == ""); - } -} - -TEST_CASE("ucl: integer: comparison", "[ucl]") -{ - using namespace nihil::ucl; - - auto i = 42_ucl; - - SECTION("operator==") { - REQUIRE(i == 42); - REQUIRE(i == 42_ucl); - } - - SECTION("operator!=") { - REQUIRE(i != 1); - REQUIRE(i != 1_ucl); - } - - SECTION("operator<") { - REQUIRE(i < 43); - REQUIRE(i < 43_ucl); - } - - SECTION("operator>") { - REQUIRE(i > 1); - REQUIRE(i > 1_ucl); - } -} - -TEST_CASE("ucl: integer: parse", "[ucl]") -{ - using namespace nihil::ucl; - - auto obj = parse("value = 42").value(); - - auto v = obj["value"]; - REQUIRE(v.key() == "value"); - REQUIRE(object_cast(v) == 42); -} - -TEST_CASE("ucl: integer: parse and emit", "[ucl]") -{ - using namespace nihil::ucl; - - auto ucl = parse("int = 42;").value(); - - auto output = std::string(); - emit(ucl, emitter::configuration, std::back_inserter(output)); - - REQUIRE(output == "int = 42;\n"); -} - -TEST_CASE("ucl: integer: format", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("bare integer") { - auto str = std::format("{}", 42_ucl); - REQUIRE(str == "42"); - } - - SECTION("parsed integer") { - auto obj = parse("int = 42;").value(); - auto i = object_cast(obj["int"]).value(); - - auto str = std::format("{}", i); - REQUIRE(str == "42"); - } - - SECTION("with format string") { - auto str = std::format("{:-05}", 42_ucl); - REQUIRE(str == "00042"); - } -} - -TEST_CASE("ucl: integer: print to ostream", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("bare integer") { - auto strm = std::ostringstream(); - strm << 42_ucl; - - REQUIRE(strm.str() == "42"); - } - - SECTION("parsed integer") { - auto obj = parse("int = 42;").value(); - auto i = object_cast(obj["int"]).value(); - - auto strm = std::ostringstream(); - strm << i; - - REQUIRE(strm.str() == "42"); - } -} diff --git a/nihil.ucl/tests/map.cc b/nihil.ucl/tests/map.cc deleted file mode 100644 index 7240cb3..0000000 --- a/nihil.ucl/tests/map.cc +++ /dev/null @@ -1,192 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -#include - -#include -#include - -import nihil.ucl; - -//NOLINTBEGIN(bugprone-unchecked-optional-access) - -TEST_CASE("ucl: map: invariants", "[ucl]") -{ - using namespace nihil::ucl; - - REQUIRE(map<>::ucl_type == object_type::object); - REQUIRE(static_cast<::ucl_type>(map<>::ucl_type) == UCL_OBJECT); - - static_assert(std::destructible>); - static_assert(std::default_initializable>); - static_assert(std::move_constructible>); - static_assert(std::copy_constructible>); - static_assert(std::equality_comparable>); - static_assert(std::totally_ordered>); - static_assert(std::swappable>); - - static_assert(std::ranges::range>); - static_assert(std::same_as, - std::ranges::range_value_t>>); -} - -TEST_CASE("ucl: map: default construct", "[ucl]") -{ - auto map = nihil::ucl::map<>(); - REQUIRE(str(map.type()) == "object"); -} - -TEST_CASE("ucl: map: construct from initializer_list", "[ucl]") -{ - using namespace nihil::ucl; - using namespace std::literals; - - auto map = nihil::ucl::map{ - {"1"sv, integer(1)}, - {"42"sv, integer(42)}, - }; - - REQUIRE(str(map.type()) == "object"); - REQUIRE(map["1"] == 1); - REQUIRE(map["42"] == 42); -} - -TEST_CASE("ucl: map: construct from range", "[ucl]") -{ - using namespace nihil::ucl; - using namespace std::literals; - - auto vec = std::vector>{ - {"1"sv, integer(1)}, - {"42"sv, integer(42)}, - }; - - auto map = nihil::ucl::map(std::from_range, vec); - - REQUIRE(str(map.type()) == "object"); - REQUIRE(map["1"] == 1); - REQUIRE(map["42"] == 42); -} - -TEST_CASE("ucl: map: construct from iterator pair", "[ucl]") -{ - using namespace nihil::ucl; - using namespace std::literals; - - auto vec = std::vector>{ - {"1"sv, integer(1)}, - {"42"sv, integer(42)}, - }; - - auto map = nihil::ucl::map(std::ranges::begin(vec), - std::ranges::end(vec)); - - REQUIRE(str(map.type()) == "object"); - REQUIRE(map["1"] == 1); - REQUIRE(map["42"] == 42); -} - -TEST_CASE("ucl: map: insert", "[ucl]") -{ - using namespace nihil::ucl; - using namespace std::literals; - - auto m = map(); - - m.insert({"test1"sv, integer(42)}); - m.insert({"test2"sv, integer(666)}); - - REQUIRE(m["test1"] == 42); - REQUIRE(m["test2"] == 666); -} - -TEST_CASE("ucl: map: find", "[ucl]") -{ - using namespace nihil::ucl; - using namespace std::literals; - - auto map = nihil::ucl::map{ - {"1"sv, integer(1)}, - {"42"sv, integer(42)}, - }; - - auto obj = map.find("42"); - REQUIRE(obj.value() == 42); - - obj = map.find("43"); - REQUIRE(!obj.has_value()); -} - -TEST_CASE("ucl: map: iterate", "[ucl]") -{ - using namespace nihil::ucl; - using namespace std::literals; - - auto map = nihil::ucl::map{ - {"1"sv, integer(1)}, - {"42"sv, integer(42)}, - }; - - auto i = 0u; - - for (auto [key, value] : map) { - if (key == "1") - REQUIRE(value == 1); - else if (key == "42") - REQUIRE(value == 42); - else - REQUIRE(false); - ++i; - } - - REQUIRE(i == 2); -} - -TEST_CASE("ucl: map: operator[] throws key_not_found", "[ucl]") -{ - auto map = nihil::ucl::map(); - REQUIRE_THROWS_AS(map["nonesuch"], nihil::ucl::key_not_found); -} - -TEST_CASE("ucl: map: remove", "[uc]") -{ - using namespace std::literals; - using namespace nihil::ucl; - - auto map = nihil::ucl::map{ - {"1"sv, integer(1)}, - {"42"sv, integer(42)}, - }; - - REQUIRE(map.find("42") != std::nullopt); - REQUIRE(map.remove("42") == true); - REQUIRE(map.find("42") == std::nullopt); - REQUIRE(map["1"] == 1); - - REQUIRE(map.remove("42") == false); -} - -TEST_CASE("ucl: map: pop", "[uc]") -{ - using namespace std::literals; - using namespace nihil::ucl; - - auto map = nihil::ucl::map{ - {"1"sv, integer(1)}, - {"42"sv, integer(42)}, - }; - - REQUIRE(map.find("42") != std::nullopt); - - auto obj = map.pop("42"); - REQUIRE(obj.value() == 42); - - REQUIRE(!map.find("42")); - REQUIRE(map["1"] == 1); - - obj = map.pop("42"); - REQUIRE(!obj); -} - -//NOLINTEND(bugprone-unchecked-optional-access) diff --git a/nihil.ucl/tests/object.cc b/nihil.ucl/tests/object.cc deleted file mode 100644 index 3ad180e..0000000 --- a/nihil.ucl/tests/object.cc +++ /dev/null @@ -1,44 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -#include - -#include - -import nihil.ucl; - -TEST_CASE("ucl object: get_ucl_object", "[ucl]") -{ - auto obj = nihil::ucl::integer(42); - - REQUIRE(obj.get_ucl_object() != nullptr); - static_assert(std::same_as<::ucl_object_t *, - decltype(obj.get_ucl_object())>); - - auto const cobj = obj; - static_assert(std::same_as<::ucl_object_t const *, - decltype(cobj.get_ucl_object())>); -} - -TEST_CASE("ucl object: compare", "[ucl]") -{ - using namespace std::literals; - - auto obj_41 = nihil::ucl::parse("int = 41;"sv); - REQUIRE(obj_41); - - auto obj_42 = nihil::ucl::parse("int = 42;"sv); - REQUIRE(obj_42); - - auto obj_42_2 = nihil::ucl::parse("int = 42;"sv); - REQUIRE(obj_42_2); - - auto obj_43 = nihil::ucl::parse("int = 43;"sv); - REQUIRE(obj_43); - - REQUIRE(*obj_42 == *obj_42_2); - REQUIRE(*obj_42 != *obj_43); - REQUIRE(*obj_42 < *obj_43); - REQUIRE(*obj_42 > *obj_41); -} diff --git a/nihil.ucl/tests/parse.cc b/nihil.ucl/tests/parse.cc deleted file mode 100644 index 43ce219..0000000 --- a/nihil.ucl/tests/parse.cc +++ /dev/null @@ -1,55 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -#include - -#include -#include - -import nihil.ucl; - -TEST_CASE("ucl parse: iterate array", "[ucl]") -{ - using namespace std::literals; - using namespace nihil::ucl; - - auto err = parse("value = [1, 42, 666];"sv); - REQUIRE(err); - - auto obj = *err; - - auto arr = obj["value"]; - REQUIRE(arr.key() == "value"); - - auto ints = object_cast>(arr); - REQUIRE(ints); - - auto vec = std::vector(std::from_range, *ints); - - REQUIRE(vec.size() == 3); - REQUIRE(vec[0] == 1); - REQUIRE(vec[1] == 42); - REQUIRE(vec[2] == 666); -} - -TEST_CASE("ucl parse: iterate hash", "[ucl]") -{ - using namespace std::literals; - using namespace nihil::ucl; - - auto input = "int = 42; bool = true; str = \"test\";"sv; - auto obj = parse(input); - REQUIRE(obj); - - for (auto &&[key, value] : *obj) { - REQUIRE(key == value.key()); - - if (key == "int") - REQUIRE(object_cast(value) == 42); - else if (key == "bool") - REQUIRE(object_cast(value) == true); - else if (key == "str") - REQUIRE(object_cast(value) == "test"); - } -} diff --git a/nihil.ucl/tests/real.cc b/nihil.ucl/tests/real.cc deleted file mode 100644 index 421917e..0000000 --- a/nihil.ucl/tests/real.cc +++ /dev/null @@ -1,248 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -#include -#include - -#include -#include -#include - -import nihil.ucl; - -TEST_CASE("ucl: real: invariants", "[ucl]") -{ - using namespace nihil::ucl; - - static_assert(std::same_as); - REQUIRE(real::ucl_type == object_type::real); - REQUIRE(static_cast<::ucl_type>(real::ucl_type) == UCL_FLOAT); - - static_assert(std::destructible); - static_assert(std::default_initializable); - static_assert(std::move_constructible); - static_assert(std::copy_constructible); - static_assert(std::equality_comparable); - static_assert(std::totally_ordered); - static_assert(std::swappable); -} - -TEST_CASE("ucl: real: constructor", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("default") { - auto r = real(); - REQUIRE(r == 0); - } - - SECTION("with value") { - auto r = real(42.1); - REQUIRE_THAT(r.value(), Catch::Matchers::WithinRel(42.1)); - } -} - -TEST_CASE("ucl: real: literal", "[ucl]") -{ - SECTION("with namespace nihil::ucl::literals") { - using namespace nihil::ucl::literals; - - auto r = 42.5_ucl; - REQUIRE(r.type() == nihil::ucl::object_type::real); - REQUIRE_THAT(r.value(), Catch::Matchers::WithinRel(42.5)); - } - - SECTION("with namespace nihil::literals") { - using namespace nihil::literals; - - auto r = 42.5_ucl; - REQUIRE(r.type() == nihil::ucl::object_type::real); - REQUIRE_THAT(r.value(), Catch::Matchers::WithinRel(42.5)); - } -} - -TEST_CASE("ucl: real: construct from UCL object", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("ref, correct type") { - auto uobj = ::ucl_object_fromdouble(42); - - auto r = real(ref, uobj); - REQUIRE(r == 42); - - ::ucl_object_unref(uobj); - } - - 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); - - REQUIRE_THROWS_AS(real(ref, uobj), type_mismatch); - - ::ucl_object_unref(uobj); - } - - SECTION("noref, wrong type") { - auto uobj = ::ucl_object_fromint(42); - - REQUIRE_THROWS_AS(real(noref, uobj), type_mismatch); - - ::ucl_object_unref(uobj); - } -} - -TEST_CASE("ucl: real: make_real", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("default value") { - auto i = make_real().value(); - REQUIRE(i == 0); - } - - SECTION("explicit value") { - auto i = make_real(42).value(); - REQUIRE(i == 42); - } -} - -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); - - swap(r1, r2); - - REQUIRE(r1 == 2.); - REQUIRE(r2 == 1.); -} - -TEST_CASE("ucl: real: value()", "[ucl]") -{ - using namespace nihil::ucl; - - auto r = 42.5_ucl; - REQUIRE_THAT(r.value(), Catch::Matchers::WithinRel(42.5)); -} - -TEST_CASE("ucl: real: key()", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("parsed with key") { - auto obj = parse("a_real = 42.5").value(); - auto r = object_cast(obj["a_real"]).value(); - REQUIRE(r.key() == "a_real"); - } - - SECTION("bare real, no key") { - auto i = 42.5_ucl; - REQUIRE(i.key() == ""); - } -} - -TEST_CASE("ucl: real: comparison", "[ucl]") -{ - using namespace nihil::ucl; - - auto i = nihil::ucl::real(42.5); - - SECTION("operator==") { - REQUIRE(i == 42.5); - REQUIRE(i == 42.5_ucl); - } - - SECTION("operator!=") { - REQUIRE(i != 1); - REQUIRE(i != 1._ucl); - } - - SECTION("operator<") { - REQUIRE(i < 43); - REQUIRE(i < 43._ucl); - } - - SECTION("operator>") { - REQUIRE(i > 1); - REQUIRE(i > 1._ucl); - } -} - -TEST_CASE("ucl: real: parse", "[ucl]") -{ - using namespace nihil::ucl; - - auto obj = parse("value = 42.1").value(); - - auto v = obj["value"]; - REQUIRE(v.key() == "value"); - REQUIRE_THAT(object_cast(v).value().value(), - Catch::Matchers::WithinRel(42.1)); -} - -TEST_CASE("ucl: real: parse and emit", "[ucl]") -{ - using namespace nihil::ucl; - - auto ucl = parse("real = 42.2").value(); - - auto output = std::string(); - emit(ucl, emitter::configuration, std::back_inserter(output)); - - REQUIRE(output == "real = 42.2;\n"); -} - -TEST_CASE("ucl: real: format", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("bare real") { - auto str = std::format("{}", 42.5_ucl); - REQUIRE(str == "42.5"); - } - - SECTION("parsed real") { - auto obj = parse("real = 42.5;").value(); - auto r = object_cast(obj["real"]).value(); - - auto str = std::format("{}", r); - REQUIRE(str == "42.5"); - } - - SECTION("with format string") { - auto str = std::format("{:10.5f}", 42.5_ucl); - REQUIRE(str == " 42.50000"); - } -} - -TEST_CASE("ucl: real: print to ostream", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("bare real") { - auto strm = std::ostringstream(); - strm << 42.5_ucl; - - REQUIRE(strm.str() == "42.5"); - } - - SECTION("parsed real") { - auto obj = parse("real = 42.5;").value(); - auto i = object_cast(obj["real"]).value(); - - auto strm = std::ostringstream(); - strm << i; - - REQUIRE(strm.str() == "42.5"); - } -} diff --git a/nihil.ucl/tests/string.cc b/nihil.ucl/tests/string.cc deleted file mode 100644 index 6409b8d..0000000 --- a/nihil.ucl/tests/string.cc +++ /dev/null @@ -1,415 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -#include -#include -#include -#include -#include - -#include -#include - -import nihil.ucl; - -TEST_CASE("ucl: string: invariants", "[ucl]") -{ - using namespace nihil::ucl; - - static_assert(std::same_as); - REQUIRE(string::ucl_type == object_type::string); - REQUIRE(static_cast<::ucl_type>(string::ucl_type) == UCL_STRING); - - static_assert(std::destructible); - static_assert(std::default_initializable); - static_assert(std::move_constructible); - static_assert(std::copy_constructible); - static_assert(std::equality_comparable); - static_assert(std::totally_ordered); - static_assert(std::swappable); - - static_assert(std::ranges::contiguous_range); - static_assert(std::same_as>); -} - -TEST_CASE("ucl: string: literal", "[ucl]") -{ - SECTION("with namespace nihil::ucl::literals") { - using namespace nihil::ucl::literals; - - auto s = "testing"_ucl; - REQUIRE(s.type() == nihil::ucl::object_type::string); - REQUIRE(s == "testing"); - } - - SECTION("with namespace nihil::literals") { - using namespace nihil::literals; - - auto s = "testing"_ucl; - REQUIRE(s.type() == nihil::ucl::object_type::string); - REQUIRE(s == "testing"); - } -} - -TEST_CASE("ucl: string: construct", "[ucl]") -{ - using namespace nihil::ucl; - using namespace std::literals; - - SECTION("empty string") { - auto str = string(); - REQUIRE(str.type() == object_type::string); - REQUIRE(str == ""); - } - - SECTION("with integer-like value") { - auto str = "42"_ucl; - REQUIRE(str.type() == object_type::string); - REQUIRE(str == "42"); - } - - SECTION("with boolean-like value") { - auto str = "true"_ucl; - REQUIRE(str.type() == object_type::string); - REQUIRE(str == "true"); - } - - SECTION("from string literal") { - auto str = string("testing"); - REQUIRE(str.type() == object_type::string); - REQUIRE(str == "testing"); - } - - SECTION("from std::string") { - auto str = string("testing"s); - REQUIRE(str.type() == object_type::string); - REQUIRE(str == "testing"); - } - - SECTION("from std::string_view") { - auto str = string("testing"sv); - 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 = string(s); - 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 = string(s); - 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 = string(s.begin(), s.end()); - 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 = string(s.begin(), s.end()); - REQUIRE(str.type() == object_type::string); - REQUIRE(str == "testing"); - } -} - -TEST_CASE("ucl: string: construct from UCL object", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("ref, correct type") { - auto uobj = ::ucl_object_fromstring("testing"); - - auto s = string(ref, uobj); - REQUIRE(s == "testing"); - - ::ucl_object_unref(uobj); - } - - SECTION("noref, correct type") { - auto uobj = ::ucl_object_fromstring("testing"); - - auto s = string(noref, uobj); - REQUIRE(s == "testing"); - } - - SECTION("ref, wrong type") { - auto uobj = ::ucl_object_frombool(true); - - REQUIRE_THROWS_AS(string(ref, uobj), type_mismatch); - - ::ucl_object_unref(uobj); - } - - SECTION("noref, wrong type") { - auto uobj = ::ucl_object_frombool(true); - - REQUIRE_THROWS_AS(string(noref, uobj), type_mismatch); - - ::ucl_object_unref(uobj); - } -} - -TEST_CASE("ucl: string: make_string", "[ucl]") -{ - using namespace nihil::ucl; - using namespace std::literals; - - SECTION("empty string") { - auto str = make_string().value(); - REQUIRE(str.type() == object_type::string); - REQUIRE(str == ""); - } - - SECTION("from string literal") { - auto 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(); - REQUIRE(str.type() == object_type::string); - REQUIRE(str == "testing"); - } - - SECTION("from std::string_view") { - auto 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(); - 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(); - 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(); - 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(); - REQUIRE(str.type() == object_type::string); - REQUIRE(str == "testing"); - } -} - -TEST_CASE("ucl: string: swap", "[ucl]") -{ - // do not add using namespace nihil::ucl - - auto s1 = nihil::ucl::string("one"); - auto s2 = nihil::ucl::string("two"); - - swap(s1, s2); - - REQUIRE(s1 == "two"); - REQUIRE(s2 == "one"); -} - -TEST_CASE("ucl: string: value()", "[ucl]") -{ - using namespace nihil::ucl; - - auto s = string("te\"st"); - REQUIRE(s.value() == "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(obj["a_string"])->key() == "a_string"); - - auto s = string("test"); - REQUIRE(s.key() == ""); -} - -TEST_CASE("ucl: string: size", "[ucl]") -{ - using namespace nihil::ucl; - - REQUIRE(string().size() == 0); - REQUIRE(string("test").size() == 4); -} - -TEST_CASE("ucl: string: empty", "[ucl]") -{ - using namespace nihil::ucl; - - REQUIRE(string().empty() == true); - REQUIRE(string("test").empty() == false); -} - -TEST_CASE("ucl: string: iterate", "[ucl]") -{ - using namespace nihil::ucl; - - auto str = "test"_ucl; - - SECTION("as iterator pair") { - auto begin = str.begin(); - static_assert(std::contiguous_iterator); - - auto end = str.end(); - static_assert(std::sentinel_for); - - REQUIRE(*begin == 't'); - ++begin; - REQUIRE(*begin == 'e'); - ++begin; - REQUIRE(*begin == 's'); - ++begin; - REQUIRE(*begin == 't'); - ++begin; - - REQUIRE(begin == end); - } - - SECTION("as range") { - auto s = std::string(std::from_range, str); - REQUIRE(s == "test"); - } -} - -TEST_CASE("ucl: string: comparison", "[ucl]") -{ - using namespace nihil::ucl; - - auto str = "testing"_ucl; - - SECTION("operator==") { - REQUIRE(str == "testing"_ucl); - REQUIRE(str == std::string_view("testing")); - REQUIRE(str == std::string("testing")); - REQUIRE(str == "testing"); - } - - SECTION("operator!=") { - REQUIRE(str != "test"_ucl); - REQUIRE(str != std::string_view("test")); - REQUIRE(str != std::string("test")); - REQUIRE(str != "test"); - } - - SECTION("operator<") { - REQUIRE(str < "zzz"_ucl); - REQUIRE(str < std::string_view("zzz")); - REQUIRE(str < std::string("zzz")); - REQUIRE(str < "zzz"); - } - - SECTION("operator>") { - REQUIRE(str > "aaa"_ucl); - REQUIRE(str > std::string_view("aaa")); - REQUIRE(str > std::string("aaa")); - REQUIRE(str > "aaa"); - } -} - -TEST_CASE("ucl: string: parse", "[ucl]") -{ - using namespace nihil::ucl; - - auto obj = parse("value = \"te\\\"st\"").value(); - - auto v = obj["value"]; - REQUIRE(v.key() == "value"); - REQUIRE(object_cast(v).value() == "te\"st"); -} - -TEST_CASE("ucl: string: emit", "[ucl]") -{ - using namespace nihil::ucl; - - auto ucl = parse("str = \"te\\\"st\";").value(); - - auto output = std::string(); - emit(ucl, emitter::configuration, std::back_inserter(output)); - - REQUIRE(output == "str = \"te\\\"st\";\n"); -} - -TEST_CASE("ucl: string: format", "[ucl]") -{ - using namespace nihil::ucl; - using namespace std::literals; - - auto constexpr test_string = "te\"st"sv; - - SECTION("bare string") { - auto str = std::format("{}", string(test_string)); - REQUIRE(str == test_string); - } - - SECTION("parsed string") { - auto obj = parse("string = \"te\\\"st\";").value(); - auto s = object_cast(obj["string"]).value(); - - auto str = std::format("{}", s); - REQUIRE(str == test_string); - } - - SECTION("with format string") { - auto str = std::format("{: >10}", string(test_string)); - REQUIRE(str == " te\"st"); - } -} - -TEST_CASE("ucl: string: print to ostream", "[ucl]") -{ - using namespace nihil::ucl; - using namespace std::literals; - - auto constexpr test_string = "te\"st"sv; - - SECTION("bare string") { - auto strm = std::ostringstream(); - strm << string(test_string); - - REQUIRE(strm.str() == test_string); - } - - SECTION("parsed string") { - auto obj = parse("string = \"te\\\"st\";").value(); - auto s = object_cast(obj["string"]).value(); - - auto strm = std::ostringstream(); - strm << s; - - REQUIRE(strm.str() == test_string); - } - - SECTION("with format string") { - auto str = std::format("{: >10}", string(test_string)); - REQUIRE(str == " te\"st"); - } -} 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 - -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 -#include -#include -#include - #include 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 +export template concept datatype = requires(T o) { { o.get_ucl_object() } -> std::convertible_to<::ucl_object_t const *>; { o.type() } -> std::same_as; @@ -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; -- cgit v1.2.3