diff options
| author | Lexi Winter <lexi@le-fay.org> | 2025-06-27 12:08:58 +0100 |
|---|---|---|
| committer | Lexi Winter <lexi@le-fay.org> | 2025-06-27 12:08:58 +0100 |
| commit | 001c9917ace09f7b1c80d96eb067e1d37e86c546 (patch) | |
| tree | 89e360961b9659a8c6b897c5412b7d6834b8eed9 /nihil.ucl | |
| parent | 90aa957ca9b7c217af7569009d1675e0f3ff8e9b (diff) | |
| download | nihil-001c9917ace09f7b1c80d96eb067e1d37e86c546.tar.gz nihil-001c9917ace09f7b1c80d96eb067e1d37e86c546.tar.bz2 | |
improve error handling
Diffstat (limited to 'nihil.ucl')
| -rw-r--r-- | nihil.ucl/CMakeLists.txt | 4 | ||||
| -rw-r--r-- | nihil.ucl/array.ccm | 251 | ||||
| -rw-r--r-- | nihil.ucl/boolean.cc | 65 | ||||
| -rw-r--r-- | nihil.ucl/boolean.ccm | 55 | ||||
| -rw-r--r-- | nihil.ucl/emit.ccm | 4 | ||||
| -rw-r--r-- | nihil.ucl/errc.cc | 51 | ||||
| -rw-r--r-- | nihil.ucl/errc.ccm | 33 | ||||
| -rw-r--r-- | nihil.ucl/error.cc | 19 | ||||
| -rw-r--r-- | nihil.ucl/error.ccm | 22 | ||||
| -rw-r--r-- | nihil.ucl/integer.cc | 68 | ||||
| -rw-r--r-- | nihil.ucl/integer.ccm | 89 | ||||
| -rw-r--r-- | nihil.ucl/map.ccm | 95 | ||||
| -rw-r--r-- | nihil.ucl/nihil.ucl.ccm | 2 | ||||
| -rw-r--r-- | nihil.ucl/object.cc | 38 | ||||
| -rw-r--r-- | nihil.ucl/object.ccm | 24 | ||||
| -rw-r--r-- | nihil.ucl/parser.cc | 4 | ||||
| -rw-r--r-- | nihil.ucl/parser.ccm | 13 | ||||
| -rw-r--r-- | nihil.ucl/real.cc | 65 | ||||
| -rw-r--r-- | nihil.ucl/real.ccm | 85 | ||||
| -rw-r--r-- | nihil.ucl/string.cc | 72 | ||||
| -rw-r--r-- | nihil.ucl/string.ccm | 196 | ||||
| -rw-r--r-- | nihil.ucl/tests/array.cc | 251 | ||||
| -rw-r--r-- | nihil.ucl/tests/boolean.cc | 168 | ||||
| -rw-r--r-- | nihil.ucl/tests/integer.cc | 208 | ||||
| -rw-r--r-- | nihil.ucl/tests/real.cc | 204 | ||||
| -rw-r--r-- | nihil.ucl/tests/string.cc | 383 | ||||
| -rw-r--r-- | nihil.ucl/type.cc | 18 | ||||
| -rw-r--r-- | nihil.ucl/type.ccm | 7 |
28 files changed, 1935 insertions, 559 deletions
diff --git a/nihil.ucl/CMakeLists.txt b/nihil.ucl/CMakeLists.txt index 0ee024c..df13e84 100644 --- a/nihil.ucl/CMakeLists.txt +++ b/nihil.ucl/CMakeLists.txt @@ -9,7 +9,7 @@ target_sources(nihil.ucl PUBLIC FILE_SET modules TYPE CXX_MODULES FILES nihil.ucl.ccm emit.ccm - error.ccm + errc.ccm object.ccm object_cast.ccm parser.ccm @@ -24,7 +24,7 @@ target_sources(nihil.ucl PRIVATE emit.cc - error.cc + errc.cc parser.cc type.cc diff --git a/nihil.ucl/array.ccm b/nihil.ucl/array.ccm index 87e175e..e3730ab 100644 --- a/nihil.ucl/array.ccm +++ b/nihil.ucl/array.ccm @@ -5,9 +5,14 @@ module; #include <cassert> +#include <cerrno> #include <cstdint> #include <cstdlib> +#include <format> +#include <iostream> #include <string> +#include <system_error> +#include <utility> #include <ucl.h> @@ -20,7 +25,7 @@ namespace nihil::ucl { export template<datatype T> struct array; -template<datatype T> +export template<datatype T> struct array_iterator { using difference_type = std::ptrdiff_t; using value_type = T; @@ -29,25 +34,39 @@ struct array_iterator { array_iterator() = default; - auto operator* (this array_iterator const &self) -> T + [[nodiscard]] auto operator* (this array_iterator const &self) -> T { - auto uobj = ::ucl_array_find_index(self._array, self._idx); + 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"); + + auto uobj = ::ucl_array_find_index(arr, self.m_idx); if (uobj == nullptr) - throw error("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); } - auto operator[] (this array_iterator const &self, - difference_type idx) + [[nodiscard]] auto operator[] (this array_iterator const &self, + difference_type idx) -> T { return *(self + idx); } - auto operator++ (this array_iterator &self) -> array_iterator& + auto operator++ (this array_iterator &self) -> array_iterator & { - ++self._idx; + auto arr = self.get_array(); + if (self.m_idx == ::ucl_array_size(arr)) + throw std::logic_error( + "nihil::ucl::array_iterator: " + "iterating past end of array"); + + ++self.m_idx; return self; } @@ -60,10 +79,11 @@ struct array_iterator { auto operator-- (this array_iterator &self) -> array_iterator& { - if (self._idx == 0) - throw std::out_of_range("attempt to iterate before " - "start of UCL array"); - --self._idx; + if (self.m_idx == 0) + throw std::logic_error( + "nihil::ucl::array_iterator: " + "iterating before start of array"); + --self.m_idx; return self; } @@ -74,65 +94,110 @@ struct array_iterator { return copy; } - auto operator== (this array_iterator const &lhs, - array_iterator const &rhs) + [[nodiscard]] auto operator== (this array_iterator const &lhs, + array_iterator const &rhs) -> bool { - return lhs._idx == rhs._idx; + // 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; } - auto operator<=> (this array_iterator const &lhs, - array_iterator const &rhs) + [[nodiscard]] auto operator<=> (this array_iterator const &lhs, + array_iterator const &rhs) { - return lhs._idx <=> rhs._idx; + // 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) + auto operator+= (this array_iterator &lhs, difference_type rhs) -> array_iterator & { - lhs._idx += rhs; + 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"); + + lhs.m_idx += rhs; return lhs; } - auto operator-= (this array_iterator &lhs, - difference_type rhs) + auto operator-= (this array_iterator &lhs, difference_type rhs) -> array_iterator & { - lhs._idx -= rhs; + if (std::cmp_greater(rhs, lhs.m_idx)) + throw std::logic_error( + "nihil::ucl::array_iterator: " + "iterating before start of array"); + lhs.m_idx -= rhs; return lhs; } - auto operator- (this array_iterator const &lhs, - array_iterator const &rhs) + [[nodiscard]] auto operator- (this array_iterator const &lhs, + array_iterator const &rhs) -> difference_type { - return lhs._idx - rhs._idx; + 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; } private: friend struct array<T>; - ::ucl_object_t const *_array{}; - std::size_t _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 * + { + if (self.m_array == nullptr) + 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(array) - , _idx(idx) + : m_array(array) + , m_idx(idx) {} }; -export template<datatype T> +export template<datatype T> [[nodiscard]] auto operator+(array_iterator<T> const &lhs, typename array_iterator<T>::difference_type rhs) - -> array_iterator<T> +-> array_iterator<T> { auto copy = lhs; copy += rhs; return copy; } -export template<datatype T> +export template<datatype T> [[nodiscard]] auto operator+(typename array_iterator<T>::difference_type lhs, array_iterator<T> const &rhs) -> array_iterator<T> @@ -140,7 +205,7 @@ auto operator+(typename array_iterator<T>::difference_type lhs, return rhs - lhs; } -export template<datatype T> +export template<datatype T> [[nodiscard]] auto operator-(array_iterator<T> const &lhs, typename array_iterator<T>::difference_type rhs) -> array_iterator<T> @@ -157,27 +222,50 @@ struct array final : object { using value_type = T; using size_type = std::size_t; using difference_type = std::ptrdiff_t; + using iterator = array_iterator<T>; - // Create an empty array. - array() : object(noref, ::ucl_object_typed_new(UCL_ARRAY)) + /* + * 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))); + return uobj; + }()) { - if (_object == nullptr) - throw error("failed to create UCL object"); } - // Create a new array from a UCL object. + /* + * 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, uobj) + : object(nihil::ucl::ref, [&] { + auto actual_type = static_cast<object_type>( + ::ucl_object_type(uobj)); + if (actual_type != array::ucl_type) + throw type_mismatch(array::ucl_type, + actual_type); + return uobj; + }()) { - if (type() != ucl_type) - throw type_mismatch(ucl_type, type()); } array(noref_t, ::ucl_object_t *uobj) - : object(noref, uobj) + : object(nihil::ucl::noref, [&] { + auto actual_type = static_cast<object_type>( + ::ucl_object_type(uobj)); + if (actual_type != array::ucl_type) + throw type_mismatch(array::ucl_type, + actual_type); + return uobj; + }()) { - if (type() != ucl_type) - throw type_mismatch(ucl_type, type()); } /* @@ -188,9 +276,6 @@ struct array final : object { array(Iterator first, Iterator last) : array() { - if (_object == nullptr) - throw error("failed to create UCL object"); - // This is exception safe, because if we throw here the // base class destructor will free the array. while (first != last) { @@ -223,12 +308,12 @@ struct array final : object { * Array iterator access. */ - auto begin(this array const &self) -> array_iterator<T> + [[nodiscard]] auto begin(this array const &self) -> iterator { return {self.get_ucl_object(), 0}; } - auto end(this array const &self) -> array_iterator<T> + [[nodiscard]] auto end(this array const &self) -> iterator { return {self.get_ucl_object(), self.size()}; } @@ -236,7 +321,7 @@ struct array final : object { /* * Return the size of this array. */ - auto size(this array const &self) -> size_type + [[nodiscard]] auto size(this array const &self) -> size_type { return ::ucl_array_size(self.get_ucl_object()); } @@ -244,7 +329,7 @@ struct array final : object { /* * Test if this array is empty. */ - auto empty(this array const &self) -> bool + [[nodiscard]] auto empty(this array const &self) -> bool { return self.size() == 0; } @@ -278,19 +363,20 @@ struct array final : object { /* * Access an array element by index. */ - auto at(this array const &self, size_type idx) -> T + [[nodiscard]] auto at(this array const &self, size_type idx) -> T { if (idx >= self.size()) throw std::out_of_range("UCL array index out of range"); auto uobj = ::ucl_array_find_index(self.get_ucl_object(), idx); if (uobj == nullptr) - throw error("failed to fetch UCL array index"); + throw std::runtime_error( + "failed to fetch UCL array index"); return T(nihil::ucl::ref, uobj); } - auto operator[] (this array const &self, size_type idx) -> T + [[nodiscard]] auto operator[] (this array const &self, size_type idx) -> T { return self.at(idx); } @@ -298,7 +384,7 @@ struct array final : object { /* * Return the first element. */ - auto front(this array const &self) -> T + [[nodiscard]] auto front(this array const &self) -> T { return self.at(0); } @@ -306,7 +392,7 @@ struct array final : object { /* * Return the last element. */ - auto back(this array const &self) -> T + [[nodiscard]] auto back(this array const &self) -> T { if (self.empty()) throw std::out_of_range("attempt to access back() on " @@ -319,7 +405,7 @@ struct array final : object { * Comparison operators. */ -export template<datatype T> +export template<datatype T> [[nodiscard]] auto operator==(array<T> const &a, array<T> const &b) -> bool { if (a.size() != b.size()) @@ -332,4 +418,51 @@ auto operator==(array<T> const &a, array<T> const &b) -> bool return true; } +/* + * Print an array to an ostream; uses the same format as std::format(). + */ +export template<datatype T> +auto operator<<(std::ostream &strm, array<T> const &a) -> std::ostream & +{ + return strm << std::format("{}", a); +} + } // namespace nihil::ucl + +/* + * std::formatter for an array. The output format is a list of values + * on a single line: [1, 2, 3]. + */ +export template<typename T> +struct std::formatter<nihil::ucl::array<T>, char> +{ + template<class ParseContext> + constexpr ParseContext::iterator parse(ParseContext& ctx) + { + return ctx.begin(); + } + + template<class FmtContext> + FmtContext::iterator format(nihil::ucl::array<T> const &o, + FmtContext& ctx) const + { + auto it = ctx.out(); + bool first = true; + + *it++ = '['; + + for (auto &&elm : o) { + if (first) + first = false; + else { + *it++ = ','; + *it++ = ' '; + } + + it = std::format_to(it, "{}", elm); + } + + *it++ = ']'; + return it; + } +}; diff --git a/nihil.ucl/boolean.cc b/nihil.ucl/boolean.cc index 95b4e2f..2a643b9 100644 --- a/nihil.ucl/boolean.cc +++ b/nihil.ucl/boolean.cc @@ -6,40 +6,69 @@ module; #include <compare> #include <cstdlib> +#include <expected> +#include <system_error> #include <ucl.h> module nihil.ucl; +import nihil; + namespace nihil::ucl { -boolean::boolean() : boolean(false) +auto make_boolean(boolean::contained_type value) + -> std::expected<boolean, error> { + auto *uobj = ::ucl_object_frombool(value); + if (uobj == nullptr) + return std::unexpected(error( + errc::failed_to_create_object, + error(std::errc(errno)))); + + return boolean(noref, uobj); } -boolean::boolean(ref_t, ::ucl_object_t const *uobj) - : object(nihil::ucl::ref, uobj) +boolean::boolean() + : boolean(false) { - if (type() != ucl_type) - throw type_mismatch(ucl_type, type()); } -boolean::boolean(noref_t, ::ucl_object_t *uobj) - : object(noref, uobj) +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; + }()) { - if (type() != ucl_type) - throw type_mismatch(ucl_type, type()); } -boolean::boolean(contained_type value) - : object(noref, ::ucl_object_frombool(value)) +boolean::boolean(ref_t, ::ucl_object_t const *uobj) + : object(nihil::ucl::ref, [&] { + auto actual_type = static_cast<object_type>( + ::ucl_object_type(uobj)); + if (actual_type != boolean::ucl_type) + throw type_mismatch(boolean::ucl_type, actual_type); + return uobj; + }()) +{ +} + +boolean::boolean(noref_t, ::ucl_object_t *uobj) + : object(nihil::ucl::noref, [&] { + auto actual_type = static_cast<object_type>( + ::ucl_object_type(uobj)); + if (actual_type != boolean::ucl_type) + throw type_mismatch(boolean::ucl_type, actual_type); + return uobj; + }()) { - if (_object == nullptr) - throw error("failed to create UCL object"); } auto boolean::value(this boolean const &self) --> contained_type + -> contained_type { auto v = contained_type{}; auto const *uobj = self.get_ucl_object(); @@ -51,25 +80,25 @@ auto boolean::value(this boolean const &self) } auto operator== (boolean const &a, boolean const &b) --> bool + -> bool { return a.value() == b.value(); } auto operator<=> (boolean const &a, boolean const &b) --> std::strong_ordering + -> std::strong_ordering { return a.value() <=> b.value(); } auto operator== (boolean const &a, boolean::contained_type b) --> bool + -> bool { return a.value() == b; } auto operator<=> (boolean const &a, boolean::contained_type b) --> std::strong_ordering + -> std::strong_ordering { return a.value() <=> b; } diff --git a/nihil.ucl/boolean.ccm b/nihil.ucl/boolean.ccm index 78ede17..068dfdd 100644 --- a/nihil.ucl/boolean.ccm +++ b/nihil.ucl/boolean.ccm @@ -7,6 +7,8 @@ module; #include <cassert> #include <cstdint> #include <cstdlib> +#include <expected> +#include <format> #include <string> #include <ucl.h> @@ -22,21 +24,37 @@ export struct boolean final : object { inline static constexpr object_type ucl_type = object_type::boolean; - // Create a new boolean from a UCL object. - boolean(ref_t, ::ucl_object_t const *uobj); - boolean(noref_t, ::ucl_object_t *uobj); - - // Create a new default-initialised boolean. + /* + * Create a boolean holding the value false. Throws std::system_error + * on failure. + */ boolean(); - // Create a new boolean from a value. - explicit boolean(contained_type value); + /* + * Create a boolean holding a specific value. Throws std::system_error + * on failure. + */ + explicit boolean(bool); + + /* + * 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); // Return this object's value. auto value(this boolean const &self) -> contained_type; }; /* + * Boolean constructors. These return an error instead of throwing. + */ + +export [[nodiscard]] auto +make_boolean(boolean::contained_type = false) -> std::expected<boolean, error>; + +/* * Comparison operators. */ @@ -48,3 +66,26 @@ export auto operator<=> (boolean const &a, boolean::contained_type b) -> std::strong_ordering; } // namespace nihil::ucl + +/* + * std::formatter for a boolean. This provides the same format operations + * as std::formatter<bool>. + */ +export template<> +struct std::formatter<nihil::ucl::boolean, char> +{ + std::formatter<bool> base_formatter; + + template<class ParseContext> + constexpr ParseContext::iterator parse(ParseContext& ctx) + { + return base_formatter.parse(ctx); + } + + template<class FmtContext> + FmtContext::iterator format(nihil::ucl::boolean const &o, + FmtContext& ctx) const + { + return base_formatter.format(o.value(), ctx); + } +}; diff --git a/nihil.ucl/emit.ccm b/nihil.ucl/emit.ccm index 849c5a7..b88f8e7 100644 --- a/nihil.ucl/emit.ccm +++ b/nihil.ucl/emit.ccm @@ -130,7 +130,7 @@ private: export auto emit(object const &object, emitter format, std::output_iterator<char> auto &&it) --> void + -> void { auto ucl_format = static_cast<ucl_emitter>(format); auto wrapper = emit_wrapper(it); @@ -144,7 +144,7 @@ export auto emit(object const &object, emitter format, * Basic ostream printer for UCL; default to JSON since it's probably what * most people expect. */ -export auto operator<<(std::ostream &stream, object const &o) -> std::ostream &; +export auto operator<<(std::ostream &, object const &) -> std::ostream &; } // namespace nihil::ucl diff --git a/nihil.ucl/errc.cc b/nihil.ucl/errc.cc new file mode 100644 index 0000000..fc1d9f8 --- /dev/null +++ b/nihil.ucl/errc.cc @@ -0,0 +1,51 @@ +/* + * This source code is released into the public domain. + */ + +module; + +#include <string> +#include <system_error> + +module nihil.ucl; + +import nihil; + +namespace nihil::ucl { + +struct ucl_error_category final : std::error_category { + auto name() const noexcept -> char const * override; + auto message(int err) const -> std::string override; +}; + +auto ucl_category() noexcept -> std::error_category & +{ + static auto category = ucl_error_category(); + return category; +} + +auto make_error_condition(errc ec) -> std::error_condition +{ + return {static_cast<int>(ec), ucl_category()}; +} + +auto ucl_error_category::name() const noexcept -> char const * +{ + return "nihil.ucl"; +} + +auto ucl_error_category::message(int err) const -> std::string +{ + switch (static_cast<errc>(err)) { + case errc::no_error: + return "No error"; + case errc::failed_to_create_object: + return "Failed to create UCL object"; + case errc::type_mismatch: + return "UCL type does not match expected type"; + default: + return "Undefined error"; + } +} + +} // namespace nihil::ucl diff --git a/nihil.ucl/errc.ccm b/nihil.ucl/errc.ccm new file mode 100644 index 0000000..8f0444d --- /dev/null +++ b/nihil.ucl/errc.ccm @@ -0,0 +1,33 @@ +/* + * This source code is released into the public domain. + */ + +module; + +#include <string> +#include <system_error> + +export module nihil.ucl:errc; + +namespace nihil::ucl { + +export enum struct errc { + no_error = 0, + + // ucl_object_new() or similar failed, e.g. out of memory + failed_to_create_object, + // Trying to create an object from a UCL object of the wrong type + type_mismatch, +}; + +export auto ucl_category() noexcept -> std::error_category &; +export auto make_error_condition(errc ec) -> std::error_condition; + +} // namespace nihil::ucl + +namespace std { + +export template<> +struct is_error_condition_enum<nihil::ucl::errc> : true_type {}; + +} // namespace std diff --git a/nihil.ucl/error.cc b/nihil.ucl/error.cc deleted file mode 100644 index 2f19cb7..0000000 --- a/nihil.ucl/error.cc +++ /dev/null @@ -1,19 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include <string> -#include <utility> - -module nihil.ucl; - -namespace nihil::ucl { - -error::error(std::string what) - : generic_error(std::move(what)) -{ -} - -} // namespace nihil::ucl diff --git a/nihil.ucl/error.ccm b/nihil.ucl/error.ccm deleted file mode 100644 index cdb5c2b..0000000 --- a/nihil.ucl/error.ccm +++ /dev/null @@ -1,22 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include <string> - -export module nihil.ucl:error; - -import nihil; - -namespace nihil::ucl { - -/* - * Exception thrown when an issue occurs with UCL. - */ -export struct error : generic_error { - error(std::string what); -}; - -} // namespace nihil::ucl diff --git a/nihil.ucl/integer.cc b/nihil.ucl/integer.cc index 16328d4..f4f08ef 100644 --- a/nihil.ucl/integer.cc +++ b/nihil.ucl/integer.cc @@ -6,36 +6,65 @@ module; #include <compare> #include <cstdlib> +#include <expected> +#include <system_error> #include <ucl.h> module nihil.ucl; +import nihil; + 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, uobj) + : object(nihil::ucl::ref, [&] { + auto actual_type = static_cast<object_type>( + ::ucl_object_type(uobj)); + if (actual_type != integer::ucl_type) + throw type_mismatch(integer::ucl_type, actual_type); + return uobj; + }()) { - if (type() != ucl_type) - throw type_mismatch(ucl_type, type()); } integer::integer(noref_t, ::ucl_object_t *uobj) - : object(noref, uobj) + : object(nihil::ucl::noref, [&] { + auto actual_type = static_cast<object_type>( + ::ucl_object_type(uobj)); + if (actual_type != integer::ucl_type) + throw type_mismatch(integer::ucl_type, actual_type); + return uobj; + }()) { - if (type() != ucl_type) - throw type_mismatch(ucl_type, type()); } -integer::integer() - : integer(0) -{} - -integer::integer(contained_type value) - : object(noref, ::ucl_object_fromint(value)) +auto make_integer(integer::contained_type value) + -> std::expected<integer, error> { - if (_object == nullptr) - throw error("failed to create UCL object"); + 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 @@ -49,26 +78,23 @@ auto integer::value(this integer const &self) -> contained_type std::abort(); } -auto operator== (integer const &a, integer const &b) --> bool +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 +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 +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 + -> std::strong_ordering { return a.value() <=> b; } diff --git a/nihil.ucl/integer.ccm b/nihil.ucl/integer.ccm index 7a87df3..d5ac72a 100644 --- a/nihil.ucl/integer.ccm +++ b/nihil.ucl/integer.ccm @@ -7,11 +7,15 @@ module; #include <compare> #include <cstdint> #include <cstdlib> +#include <expected> +#include <format> +#include <utility> #include <ucl.h> export module nihil.ucl:integer; +import nihil; import :object; import :type; @@ -21,29 +25,92 @@ export struct integer final : object { using contained_type = std::int64_t; inline static constexpr object_type ucl_type = object_type::integer; - // Create a new integer from a UCL object. - integer(ref_t, ::ucl_object_t const *uobj); - integer(noref_t, ::ucl_object_t *uobj); - - // Create a new default-initialised integer. + /* + * Create an integer holding the value 0. Throws std::system_error + * on failure. + */ integer(); - // Create a new integer from a value. + /* + * 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); + // Return the value of this object. - auto value(this integer const &self) -> contained_type; + [[nodiscard]] auto value(this integer const &self) -> contained_type; }; /* + * Integer constructors. These return an error instead of throwing. + */ + +export [[nodiscard]] auto +make_integer(integer::contained_type = 0) -> std::expected<integer, error>; + +/* * Comparison operators. */ -export auto operator== (integer const &a, integer const &b) -> bool; -export auto operator== (integer const &a, integer::contained_type b) -> bool; -export auto operator<=> (integer const &a, integer const &b) +export [[nodiscard]] auto operator== (integer const &a, + integer const &b) -> bool; + +export [[nodiscard]] auto operator== (integer const &a, + integer::contained_type b) -> bool; + +export [[nodiscard]] auto operator<=> (integer const &a, + integer const &b) -> std::strong_ordering; -export auto operator<=> (integer const &a, integer::contained_type b) + +export [[nodiscard]] auto operator<=> (integer const &a, + integer::contained_type b) -> std::strong_ordering; +/* + * Literal operator. + */ +inline namespace literals { +export constexpr auto operator""_ucl (unsigned long long i) -> integer +{ + if (std::cmp_greater(i, std::numeric_limits<std::int64_t>::max())) + throw std::out_of_range("literal out of range"); + + return integer(i); +} +} // namespace nihil::ucl::literals + } // namespace nihil::ucl + +namespace nihil { inline namespace literals { + export using namespace ::nihil::ucl::literals; +}} // namespace nihil::literals + +/* + * std::formatter for an integer. This provides the same format operations + * as std::formatter<std::int64_t>. + */ +export template<> +struct std::formatter<nihil::ucl::integer, char> +{ + std::formatter<std::int64_t> base_formatter; + + template<class ParseContext> + constexpr ParseContext::iterator parse(ParseContext& ctx) + { + return base_formatter.parse(ctx); + } + + template<class FmtContext> + FmtContext::iterator format(nihil::ucl::integer const &o, + FmtContext& ctx) const + { + return base_formatter.format(o.value(), ctx); + } +}; diff --git a/nihil.ucl/map.ccm b/nihil.ucl/map.ccm index 434659b..1c5dd19 100644 --- a/nihil.ucl/map.ccm +++ b/nihil.ucl/map.ccm @@ -11,6 +11,7 @@ module; #include <memory> #include <optional> #include <string> +#include <system_error> #include <ucl.h> @@ -24,16 +25,16 @@ namespace nihil::ucl { export struct key_not_found : error { key_not_found(std::string_view key) : error(std::format("key '{}' not found in map", key)) - , _key(key) + , m_key(key) {} auto key(this key_not_found const &self) -> std::string_view { - return self._key; + return self.m_key; } private: - std::string _key; + std::string m_key; }; export template<datatype T> @@ -50,25 +51,28 @@ struct map_iterator { struct sentinel{}; - auto operator==(this map_iterator const &self, sentinel) -> bool + [[nodiscard]] auto operator==(this map_iterator const &self, sentinel) + -> bool { - return (self._state->cur == nullptr); + return (self.m_state->cur == nullptr); } auto operator++(this map_iterator &self) -> map_iterator & { - self._state->next(); + self.m_state->next(); return self; } auto operator++(this map_iterator &self, int) -> map_iterator & { - self._state->next(); + self.m_state->next(); return self; } - auto operator*(this map_iterator const &self) -> value_type { - auto obj = T(ref, self._state->cur); + [[nodiscard]] auto operator*(this map_iterator const &self) + -> value_type + { + auto obj = T(ref, self.m_state->cur); return {obj.key(), std::move(obj)}; } @@ -76,8 +80,8 @@ private: friend struct map<T>; map_iterator(::ucl_object_t const *obj) + : m_state(std::make_shared<state>(obj)) { - _state = std::make_shared<state>(obj); ++(*this); } @@ -85,18 +89,21 @@ private: state(::ucl_object_t const *obj) { if ((iter = ::ucl_object_iterate_new(obj)) == nullptr) - throw error("failed to create UCL iterator"); + throw std::system_error(make_error_code( + std::errc(errno))); } state(state const &) = delete; auto operator=(this state &, state const &) -> state& = delete; - ~state() { + ~state() + { if (iter != nullptr) ::ucl_object_iterate_free(iter); } - auto next() -> void { + auto next() -> void + { cur = ::ucl_object_iterate_safe(iter, true); } @@ -104,7 +111,7 @@ private: ucl_object_t const *cur = nullptr; }; - std::shared_ptr<state> _state; + std::shared_ptr<state> m_state; }; export template<datatype T = object> @@ -116,26 +123,49 @@ struct map final : object { using difference_type = std::ptrdiff_t; using iterator = map_iterator<T>; - // Create an empty map - map() : object(noref, ::ucl_object_typed_new(UCL_OBJECT)) + /* + * Create an empty map. Throws std::system_error on failure. + */ + map() : object(noref, [] { + auto *uobj = ::ucl_object_typed_new(UCL_OBJECT); + if (uobj == nullptr) + throw std::system_error( + std::make_error_code(std::errc(errno))); + return uobj; + }()) { - if (_object == nullptr) - throw error("failed to create UCL object"); } - // Create a new map from a UCL object. + /* + * 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, uobj) + : object(nihil::ucl::ref, [&] { + auto actual_type = static_cast<object_type>( + ::ucl_object_type(uobj)); + if (actual_type != map::ucl_type) + throw type_mismatch(map::ucl_type, + actual_type); + return uobj; + }()) { if (type() != ucl_type) throw type_mismatch(ucl_type, type()); } map(noref_t, ::ucl_object_t *uobj) - : object(noref, uobj) + : object(nihil::ucl::noref, [&] { + auto actual_type = static_cast<object_type>( + ::ucl_object_type(uobj)); + if (actual_type != map::ucl_type) + throw type_mismatch(map::ucl_type, + actual_type); + return uobj; + }()) { - if (type() != ucl_type) - throw type_mismatch(ucl_type, type()); } /* @@ -146,9 +176,6 @@ struct map final : object { map(Iterator first, Iterator last) : map() { - if (_object == nullptr) - throw error("failed to create UCL object"); - // This is exception safe, because if we throw here the // base class destructor will free the map. while (first != last) { @@ -165,7 +192,7 @@ struct map final : object { value_type>) map(std::from_range_t, Range &&range) : map(std::ranges::begin(range), - std::ranges::end(range)) + std::ranges::end(range)) { } @@ -181,12 +208,12 @@ struct map final : object { * Map iterator access. */ - auto begin(this map const &self) -> iterator + [[nodiscard]] auto begin(this map const &self) -> iterator { return {self.get_ucl_object()}; } - auto end(this map const &) -> iterator::sentinel + [[nodiscard]] auto end(this map const &) -> iterator::sentinel { return {}; } @@ -213,7 +240,7 @@ struct map final : object { /* * Access a map element by key. */ - auto find(this map const &self, std::string_view key) + [[nodiscard]] auto find(this map const &self, std::string_view key) -> std::optional<T> { auto const *obj = ::ucl_object_lookup_len( @@ -247,7 +274,13 @@ struct map final : object { return {}; } - 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 key) + -> T { auto obj = self.find(key); if (obj) diff --git a/nihil.ucl/nihil.ucl.ccm b/nihil.ucl/nihil.ucl.ccm index 9c2ea88..b16eb3d 100644 --- a/nihil.ucl/nihil.ucl.ccm +++ b/nihil.ucl/nihil.ucl.ccm @@ -7,7 +7,7 @@ module; export module nihil.ucl; export import :emit; -export import :error; +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 index f435b90..ee4968b 100644 --- a/nihil.ucl/object.cc +++ b/nihil.ucl/object.cc @@ -12,29 +12,31 @@ module; module nihil.ucl; +import nihil; + namespace nihil::ucl { object::object(ref_t, ::ucl_object_t const *object) - : _object(::ucl_object_ref(object)) + : m_object(::ucl_object_ref(object)) { } object::object(noref_t, ::ucl_object_t *object) - : _object(object) + : m_object(object) { } object::~object() { - if (_object != nullptr) - ::ucl_object_unref(_object); + if (m_object != nullptr) + ::ucl_object_unref(m_object); } object::object(object &&other) noexcept - : _object(std::exchange(other._object, nullptr)) + : m_object(std::exchange(other.m_object, nullptr)) {} object::object(object const &other) noexcept - : _object(nullptr) + : m_object(nullptr) { *this = other; } @@ -43,7 +45,7 @@ auto object::operator=(this object &self, object &&other) noexcept -> object & { if (&self != &other) - self._object = std::exchange(other._object, nullptr); + self.m_object = std::exchange(other.m_object, nullptr); return self; } @@ -53,11 +55,11 @@ auto object::operator=(this object &self, object const &other) if (&self != &other) { auto *new_uobj = ::ucl_object_copy(other.get_ucl_object()); if (new_uobj == nullptr) - throw error("failed to copy UCL object"); + throw std::runtime_error("failed to copy UCL object"); - if (self._object != nullptr) - ::ucl_object_unref(self._object); - self._object = new_uobj; + if (self.m_object != nullptr) + ::ucl_object_unref(self.m_object); + self.m_object = new_uobj; } return self; @@ -76,16 +78,16 @@ auto object::type(this object const &self) -> object_type auto object::get_ucl_object(this object &self) -> ::ucl_object_t * { - if (self._object == nullptr) - throw error("attempt to access empty UCL object"); - return self._object; + 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._object == nullptr) - throw error("attempt to access empty UCL object"); - return self._object; + 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. @@ -99,7 +101,7 @@ auto object::key(this object const &self) -> std::string_view auto swap(object &a, object &b) -> void { - std::swap(a._object, b._object); + std::swap(a.m_object, b.m_object); } auto operator<=>(object const &lhs, object const &rhs) -> std::strong_ordering diff --git a/nihil.ucl/object.ccm b/nihil.ucl/object.ccm index 9b48256..40f2088 100644 --- a/nihil.ucl/object.ccm +++ b/nihil.ucl/object.ccm @@ -16,11 +16,9 @@ module; #include <ucl.h> -import nihil; - export module nihil.ucl:object; -import :error; +import nihil; import :type; namespace nihil::ucl { @@ -56,22 +54,24 @@ export struct object { auto operator=(this object &self, object const &other) -> object &; // Increase the refcount of this object. - auto ref(this object const &self) -> object; + [[nodiscard]] auto ref(this object const &self) -> object; // Return the type of this object. - auto type(this object const &self) -> object_type; + [[nodiscard]] auto type(this object const &self) -> object_type; // Return the underlying object. - auto get_ucl_object(this object &self) -> ::ucl_object_t *; + [[nodiscard]] auto get_ucl_object(this object &self) + -> ::ucl_object_t *; - auto get_ucl_object(this object const &self) -> ::ucl_object_t const *; + [[nodiscard]] auto get_ucl_object(this object const &self) + -> ::ucl_object_t const *; // Return the key of this object. - auto key(this object const &self) -> std::string_view; + [[nodiscard]] auto key(this object const &self) -> std::string_view; protected: // The object we're wrapping. - ::ucl_object_t *_object = nullptr; + ::ucl_object_t *m_object = nullptr; friend auto swap(object &a, object &b) -> void; }; @@ -80,8 +80,10 @@ protected: * Object comparison. */ -export auto operator==(object const &lhs, object const &rhs) -> bool; -export auto operator<=>(object const &lhs, object const &rhs) +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/parser.cc b/nihil.ucl/parser.cc index 611fe50..2288c4c 100644 --- a/nihil.ucl/parser.cc +++ b/nihil.ucl/parser.cc @@ -16,14 +16,14 @@ import nihil; namespace nihil::ucl { -auto make_parser(int flags) -> std::expected<parser, nihil::error> +auto make_parser(int flags) -> std::expected<parser, error> { auto *p = ::ucl_parser_new(flags); if (p != nullptr) return p; // TODO: Is there a way to get the actual error here? - return std::unexpected(nihil::error("failed to create parser")); + return std::unexpected(error("failed to create parser")); } auto macro_handler::handle(unsigned char const *data, diff --git a/nihil.ucl/parser.ccm b/nihil.ucl/parser.ccm index efddd5f..2630c81 100644 --- a/nihil.ucl/parser.ccm +++ b/nihil.ucl/parser.ccm @@ -17,7 +17,6 @@ module; export module nihil.ucl:parser; import nihil; -import :error; import :object; import :map; @@ -92,7 +91,7 @@ struct parser { // Add data to the parser. [[nodiscard]] auto add(this parser &self, std::ranges::contiguous_range auto &&data) - -> std::expected<void, nihil::error> + -> std::expected<void, error> // Only bytes (chars) are permitted. requires(sizeof(std::ranges::range_value_t<decltype(data)>) == 1) { @@ -106,12 +105,12 @@ struct parser { if (ret == true) return {}; - return std::unexpected(nihil::error(::ucl_parser_get_error(p))); + return std::unexpected(error(::ucl_parser_get_error(p))); } [[nodiscard]] auto add(this parser &self, std::ranges::range auto &&data) - -> std::expected<void, nihil::error> + -> std::expected<void, error> requires (!std::ranges::contiguous_range<decltype(data)>) { auto cdata = std::vector<char>( @@ -139,12 +138,12 @@ private: // Create a parser with the given flags. export [[nodiscard]] auto -make_parser(int flags = 0) -> std::expected<parser, nihil::error>; +make_parser(int flags = 0) -> std::expected<parser, error>; // Utility function to parse something and return the top-level object. export [[nodiscard]] auto parse(int flags, std::ranges::range auto &&data) - -> std::expected<map<object>, nihil::error> + -> std::expected<map<object>, error> { auto p = co_await make_parser(flags); co_await p.add(std::forward<decltype(data)>(data)); @@ -153,7 +152,7 @@ parse(int flags, std::ranges::range auto &&data) export [[nodiscard]] auto parse(std::ranges::range auto &&data) - -> std::expected<map<object>, nihil::error> + -> std::expected<map<object>, error> { co_return co_await parse(0, std::forward<decltype(data)>(data)); } diff --git a/nihil.ucl/real.cc b/nihil.ucl/real.cc index b371072..b3d50c3 100644 --- a/nihil.ucl/real.cc +++ b/nihil.ucl/real.cc @@ -7,26 +7,28 @@ module; #include <cassert> #include <compare> #include <cstdlib> +#include <expected> #include <string> +#include <system_error> #include <ucl.h> module nihil.ucl; +import nihil; + namespace nihil::ucl { -real::real(ref_t, ::ucl_object_t const *uobj) - : object(nihil::ucl::ref, uobj) +auto make_real(real::contained_type value) + -> std::expected<real, error> { - if (type() != ucl_type) - throw type_mismatch(ucl_type, type()); -} + auto *uobj = ::ucl_object_fromdouble(value); + if (uobj == nullptr) + return std::unexpected(error( + errc::failed_to_create_object, + error(std::errc(errno)))); -real::real(noref_t, ::ucl_object_t *uobj) - : object(noref, uobj) -{ - if (type() != ucl_type) - throw type_mismatch(ucl_type, type()); + return real(noref, uobj); } real::real() @@ -35,10 +37,36 @@ real::real() } real::real(contained_type value) - : object(noref, ::ucl_object_fromdouble(value)) + : real(noref, [&] { + auto *uobj = ::ucl_object_fromdouble(value); + if (uobj == nullptr) + throw std::system_error( + std::make_error_code(std::errc(errno))); + return uobj; + }()) +{ +} + +real::real(ref_t, ::ucl_object_t const *uobj) + : object(nihil::ucl::ref, [&] { + auto actual_type = static_cast<object_type>( + ::ucl_object_type(uobj)); + if (actual_type != real::ucl_type) + throw type_mismatch(real::ucl_type, actual_type); + return uobj; + }()) +{ +} + +real::real(noref_t, ::ucl_object_t *uobj) + : object(nihil::ucl::noref, [&] { + auto actual_type = static_cast<object_type>( + ::ucl_object_type(uobj)); + if (actual_type != real::ucl_type) + throw type_mismatch(real::ucl_type, actual_type); + return uobj; + }()) { - if (_object == nullptr) - throw error("failed to create UCL object"); } auto real::value(this real const &self) -> contained_type @@ -52,26 +80,23 @@ auto real::value(this real const &self) -> contained_type std::abort(); } -auto operator== (real const &a, real const &b) --> bool +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 +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 +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 + -> std::partial_ordering { return a.value() <=> b; } diff --git a/nihil.ucl/real.ccm b/nihil.ucl/real.ccm index 4e2748b..c491553 100644 --- a/nihil.ucl/real.ccm +++ b/nihil.ucl/real.ccm @@ -5,6 +5,9 @@ module; #include <compare> +#include <expected> +#include <format> +#include <utility> #include <ucl.h> @@ -20,30 +23,90 @@ export struct real final : object { inline static constexpr object_type ucl_type = object_type::real; - // Create a new real from a UCL object. - real(ref_t, ::ucl_object_t const *uobj); - real(noref_t, ::ucl_object_t *uobj); - - // Create a default-initialised real. + /* + * Create a real holding the value 0. Throws std::system_error + * on failure. + */ real(); - // Create a new real from a value. + /* + * Create a real holding a specific value. Throws std::system_error + * on failure. + */ explicit real(contained_type value); + /* + * 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); + // Return the value of this real. - auto value(this real const &self) -> contained_type; + [[nodiscard]] auto value(this real const &self) -> contained_type; }; /* + * Real constructors. These return an error instead of throwing. + */ + +export [[nodiscard]] auto +make_real(real::contained_type = 0) -> std::expected<real, error>; + +/* * Comparison operators. */ -export auto operator== (real const &a, real const &b) -> bool; -export auto operator== (real const &a, real::contained_type b) -> bool; +export [[nodiscard]] auto operator== (real const &a, real const &b) -> bool; + +export [[nodiscard]] auto operator== (real const &a, + real::contained_type b) -> bool; -export auto operator<=> (real const &a, real const &b) +export [[nodiscard]] auto operator<=> (real const &a, real const &b) -> std::partial_ordering; -export auto operator<=> (real const &a, real::contained_type b) + +export [[nodiscard]] auto operator<=> (real const &a, real::contained_type b) -> std::partial_ordering; +/* + * Literal operator. + */ +inline namespace literals { +export constexpr auto operator""_ucl (long double d) -> real +{ + if (d > static_cast<long double>(std::numeric_limits<double>::max()) || + d < static_cast<long double>(std::numeric_limits<double>::min())) + throw std::out_of_range("literal out of range"); + + return real(d); +} +} // namespace nihil::ucl::literals + } // namespace nihil::ucl + +namespace nihil { inline namespace literals { + export using namespace ::nihil::ucl::literals; +}} // namespace nihil::literals + +/* + * std::formatter for a real. This provides the same format operations + * as std::formatter<double>; + */ +export template<> +struct std::formatter<nihil::ucl::real, char> +{ + std::formatter<double> base_formatter; + + template<class ParseContext> + constexpr ParseContext::iterator parse(ParseContext& ctx) + { + return base_formatter.parse(ctx); + } + + template<class FmtContext> + FmtContext::iterator format(nihil::ucl::real const &o, + FmtContext& ctx) const + { + return base_formatter.format(o.value(), ctx); + } +}; diff --git a/nihil.ucl/string.cc b/nihil.ucl/string.cc index d2f4618..0fc9808 100644 --- a/nihil.ucl/string.cc +++ b/nihil.ucl/string.cc @@ -5,26 +5,62 @@ module; #include <cstdlib> +#include <expected> +#include <iosfwd> #include <string> +#include <system_error> #include <ucl.h> module nihil.ucl; +import nihil; + namespace nihil::ucl { +auto make_string() -> std::expected<string, error> +{ + return make_string(std::string_view("")); +} + +auto make_string(char const *s) -> std::expected<string, error> +{ + return make_string(std::string_view(s)); +} + +auto make_string(std::string_view s) -> std::expected<string, error> +{ + auto *uobj = ::ucl_object_fromstring_common( + s.data(), s.size(), UCL_STRING_RAW); + + if (uobj == nullptr) + return std::unexpected(error( + errc::failed_to_create_object, + error(std::errc(errno)))); + + return string(noref, uobj); +} + string::string(ref_t, ::ucl_object_t const *uobj) - : object(nihil::ucl::ref, uobj) + : object(nihil::ucl::ref, [&] { + auto actual_type = static_cast<object_type>( + ::ucl_object_type(uobj)); + if (actual_type != string::ucl_type) + throw type_mismatch(string::ucl_type, actual_type); + return uobj; + }()) { - if (type() != ucl_type) - throw type_mismatch(ucl_type, type()); } string::string(noref_t, ::ucl_object_t *uobj) - : object(noref, uobj) + : object(nihil::ucl::noref, [&] { + auto actual_type = static_cast<object_type>( + ::ucl_object_type(uobj)); + if (actual_type != string::ucl_type) + throw type_mismatch(string::ucl_type, actual_type); + return uobj; + }()) { - if (type() != ucl_type) - throw type_mismatch(ucl_type, type()); } string::string() @@ -32,13 +68,20 @@ string::string() {} string::string(std::string_view value) - : object(nihil::ucl::ref, - ::ucl_object_fromstring_common( - value.data(), value.size(), - UCL_STRING_RAW)) + : 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)) { - if (_object == nullptr) - throw error("failed to create UCL object"); } auto string::value(this string const &self) -> contained_type @@ -136,4 +179,9 @@ auto operator<=>(string const &lhs, char const *rhs) 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 f8dc1cd..9127b2d 100644 --- a/nihil.ucl/string.ccm +++ b/nihil.ucl/string.ccm @@ -5,13 +5,18 @@ module; #include <cstdlib> +#include <expected> +#include <format> +#include <iosfwd> #include <string> #include <ucl.h> export module nihil.ucl:string; +import nihil; import :object; +import :type; namespace nihil::ucl { @@ -19,6 +24,7 @@ export struct string final : object { using contained_type = std::string_view; inline static constexpr object_type ucl_type = object_type::string; + // string is a container of char using value_type = char const; using size_type = std::size_t; using difference_type = std::ptrdiff_t; @@ -26,57 +32,130 @@ export struct string final : object { using pointer = value_type *; using iterator = pointer; - // Create a new string from a UCL object. - string(ref_t, ::ucl_object_t const *uobj); - string(noref_t, ::ucl_object_t *uobj); - - // Create a new empty string. + /* + * Create a new empty string. Throws std::system_error on failure. + */ string(); - // Create a new UCL string from a string. - explicit string(std::string_view value); + /* + * Create a string from a value. Throws std::system_error on failure. + */ + explicit string(std::string_view); + + /* + * Create a string from a C literal. Throws std::system_error + * on failure. + */ + explicit string(char const *); + + /* + * Create a string from a contiguous range. The range's value type + * must be char. Throws std::system_error on failure. + */ + template<std::ranges::contiguous_range Range> + requires (!std::same_as<std::string_view, Range> && + std::same_as<char, std::ranges::range_value_t<Range>>) + explicit string(Range &&range) + : string(std::string_view(std::ranges::begin(range), + std::ranges::end(range))) + {} - // Create a new UCL string from an iterator pair. - template<std::contiguous_iterator Iterator> - string(Iterator first, Iterator last) - : string(std::string_view(first, last)) + /* + * Create a string from a non-contiguous range. This requires a + * temporary value due to limitations of the UCL C API. + */ + template<std::ranges::range Range> + requires (!std::ranges::contiguous_range<Range> && + std::same_as<char, std::ranges::range_value_t<Range>>) + explicit string(Range &&range) + : string(std::string(std::from_range, range)) {} + /* + * Create a string from an iterator pair. The iterator's value type + * must be char. If the iterator pair is not contiguous, the value + * will be copied to a temporary first. + * + * Throws std::system_error on failure. + */ template<std::input_iterator Iterator> - requires(!std::contiguous_iterator<Iterator>) + requires (std::same_as<char, std::iter_value_t<Iterator>>) string(Iterator first, Iterator last) - : string(std::string(first, last)) + : string(std::ranges::subrange(first, last)) {} - // Create a new UCL string from a range. - string(std::from_range_t, std::ranges::range auto &&range) - : string(std::ranges::begin(range), - std::ranges::end(range)) - {} + /* + * 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); // Return the value of this string. - auto value(this string const &self) -> contained_type; + [[nodiscard]] auto value(this string const &self) -> contained_type; // Return the size of this string. - auto size(this string const &self) -> size_type; + [[nodiscard]] auto size(this string const &self) -> size_type; // Test if this string is empty. - auto empty(this string const &self) -> bool; + [[nodiscard]] auto empty(this string const &self) -> bool; // Access this string's data - auto data(this string const &self) -> pointer; + [[nodiscard]] auto data(this string const &self) -> pointer; // Iterator access - auto begin(this string const &self) -> iterator; - auto end(this string const &self) -> iterator; + [[nodiscard]] auto begin(this string const &self) -> iterator; + [[nodiscard]] auto end(this string const &self) -> iterator; }; /* + * String constructors. These return an error instead of throwing. + */ + +// Empty string +export [[nodiscard]] auto +make_string() -> std::expected<string, error>; + +// From string_view +export [[nodiscard]] auto +make_string(std::string_view) -> std::expected<string, error>; + +// From C literal +export [[nodiscard]] auto +make_string(char const *) -> std::expected<string, error>; + +// From contiguous range +export template<std::ranges::contiguous_range Range> +requires (!std::same_as<std::string_view, Range> && + std::same_as<char, std::ranges::range_value_t<Range>>) +[[nodiscard]] auto make_string(Range &&range) +{ + return make_string(std::string_view(range)); +} + +// From non-contiguous range +export template<std::ranges::range Range> +requires (!std::ranges::contiguous_range<Range> && + std::same_as<char, std::ranges::range_value_t<Range>>) +[[nodiscard]] auto make_string(Range &&range) +{ + return make_string(std::string(std::from_range, range)); +} + +// From iterator pair +export template<std::input_iterator Iterator> +requires (std::same_as<char, std::iter_value_t<Iterator>>) +[[nodiscard]] auto make_string(Iterator first, Iterator last) +{ + return make_string(std::ranges::subrange(first, last)); +} + +/* * Comparison operators. */ -export auto operator== (string const &a, string const &b) -> bool; -export auto operator<=> (string const &a, string const &b) +export [[nodiscard]] auto operator== (string const &a, string const &b) -> bool; +export [[nodiscard]] auto operator<=> (string const &a, string const &b) -> std::strong_ordering; /* @@ -84,15 +163,68 @@ export auto operator<=> (string const &a, string const &b) * construct a temporary UCL object. */ -export auto operator==(string const &lhs, std::string_view rhs) -> bool; -export auto operator==(string const &lhs, std::string const &rhs) -> bool; -export auto operator==(string const &lhs, char const *rhs) -> bool; +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 auto operator<=>(string const &lhs, std::string_view rhs) +export [[nodiscard]] auto operator<=>(string const &lhs, + std::string_view rhs) -> std::strong_ordering; -export auto operator<=>(string const &lhs, std::string const &rhs) + +export [[nodiscard]] auto operator<=>(string const &lhs, + std::string const &rhs) -> std::strong_ordering; -export auto operator<=>(string const &lhs, char const *rhs) + +export [[nodiscard]] auto operator<=>(string const &lhs, + char const *rhs) -> std::strong_ordering; +/* + * Print a string to a stream. + */ +export auto operator<<(std::ostream &, string const &) -> std::ostream &; + +/* + * 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 + } // namespace nihil::ucl + +namespace nihil { inline namespace literals { + export using namespace ::nihil::ucl::literals; +}} // namespace nihil::literals + +/* + * std::formatter for a string. This provides the same format operations + * as std::formatter<std::string_view>. + */ +export template<> +struct std::formatter<nihil::ucl::string, char> +{ + std::formatter<std::string_view> base_formatter; + + template<class ParseContext> + constexpr ParseContext::iterator parse(ParseContext& ctx) + { + return base_formatter.parse(ctx); + } + + template<class FmtContext> + FmtContext::iterator format(nihil::ucl::string const &o, + FmtContext& ctx) const + { + return base_formatter.format(o.value(), ctx); + } +}; diff --git a/nihil.ucl/tests/array.cc b/nihil.ucl/tests/array.cc index fb23178..37928aa 100644 --- a/nihil.ucl/tests/array.cc +++ b/nihil.ucl/tests/array.cc @@ -34,49 +34,114 @@ TEST_CASE("ucl: array: invariants", "[ucl]") integer>); } -TEST_CASE("ucl: array: default construct", "[ucl]") +TEST_CASE("ucl: array: constructor", "[ucl]") { using namespace nihil::ucl; - auto arr = array<integer>(); - REQUIRE(arr.size() == 0); - REQUIRE(str(arr.type()) == "array"); + SECTION("default") { + auto arr = array<integer>(); + REQUIRE(arr.size() == 0); + REQUIRE(str(arr.type()) == "array"); + } + + SECTION("from range") { + auto vec = std::vector{integer(1), integer(42)}; + auto arr = array<integer>(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<integer>(std::ranges::begin(vec), + std::ranges::end(vec)); + + REQUIRE(arr.size() == 2); + REQUIRE(arr[0] == 1); + REQUIRE(arr[1] == 42); + } + + SECTION("from initializer_list") { + auto arr = array<integer>{integer(1), integer(42)}; + + REQUIRE(arr.size() == 2); + REQUIRE(arr[0] == 1); + REQUIRE(arr[1] == 42); + } } -TEST_CASE("ucl: array: construct from range", "[ucl]") +TEST_CASE("ucl: array: construct from UCL object", "[ucl]") { using namespace nihil::ucl; - auto vec = std::vector{integer(1), integer(42)}; - auto arr = array<integer>(std::from_range, vec); + SECTION("ref, correct type") { + auto uarr = ::ucl_object_typed_new(UCL_ARRAY); + auto uint = ::ucl_object_fromint(42); + ::ucl_array_append(uarr, uint); - REQUIRE(arr.size() == 2); - REQUIRE(arr[0] == 1); - REQUIRE(arr[1] == 42); -} + auto arr = array<integer>(ref, uarr); + REQUIRE(arr[0] == 42); -TEST_CASE("ucl: array: construct from iterator pair", "[ucl]") -{ - using namespace nihil::ucl; + ::ucl_object_unref(uarr); + } - auto vec = std::vector{integer(1), integer(42)}; - auto arr = array<integer>(std::ranges::begin(vec), - std::ranges::end(vec)); + SECTION("noref, correct type") { + auto uarr = ::ucl_object_typed_new(UCL_ARRAY); + auto uint = ::ucl_object_fromint(42); + ::ucl_array_append(uarr, uint); - REQUIRE(arr.size() == 2); - REQUIRE(arr[0] == 1); - REQUIRE(arr[1] == 42); + auto arr = array<integer>(noref, uarr); + REQUIRE(arr[0] == 42); + } + + SECTION("ref, wrong element type") { + auto uarr = ::ucl_object_typed_new(UCL_ARRAY); + auto uint = ::ucl_object_frombool(true); + ::ucl_array_append(uarr, uint); + + auto arr = array<integer>(noref, uarr); + REQUIRE_THROWS_AS(arr[0], type_mismatch); + } + + SECTION("ref, wrong type") { + auto uobj = ::ucl_object_frombool(true); + + 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: construct from initializer_list", "[ucl]") +TEST_CASE("ucl: array: swap", "[ucl]") { - using namespace nihil::ucl; + // do not add using namespace nihil::ucl - auto arr = array<integer>{integer(1), integer(42)}; + auto arr1 = nihil::ucl::array<nihil::ucl::integer>{ + nihil::ucl::integer(1), + nihil::ucl::integer(2) + }; - REQUIRE(arr.size() == 2); - REQUIRE(arr[0] == 1); - REQUIRE(arr[1] == 42); + auto arr2 = nihil::ucl::array<nihil::ucl::integer>{ + 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]") @@ -156,14 +221,10 @@ TEST_CASE("ucl: array: parse", "[ucl]") using namespace std::literals; using namespace nihil::ucl; - auto obj_err = parse("value = [1, 42, 666]"sv); - REQUIRE(obj_err); - auto obj = *obj_err; + auto obj = parse("value = [1, 42, 666]"sv).value(); - auto err = object_cast<array<integer>>(obj["value"]); - REQUIRE(err); + auto arr = object_cast<array<integer>>(obj["value"]).value(); - auto arr = *err; REQUIRE(arr.size() == 3); REQUIRE(arr[0] == 1); REQUIRE(arr[1] == 42); @@ -174,10 +235,9 @@ TEST_CASE("ucl: array: emit", "[ucl]") { using namespace nihil::ucl; - auto ucl = parse("array = [1, 42, 666];"); - REQUIRE(ucl); + auto ucl = parse("array = [1, 42, 666];").value(); - auto output = std::format("{:c}", *ucl); + auto output = std::format("{:c}", ucl); REQUIRE(output == "array [\n" " 1,\n" @@ -186,6 +246,65 @@ TEST_CASE("ucl: array: emit", "[ucl]") "]\n"); } +TEST_CASE("ucl: array: format", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION("empty array") { + auto arr = array<integer>(); + REQUIRE(std::format("{}", arr) == "[]"); + } + + SECTION("bare array") { + auto arr = array<integer>{ + integer(1), integer(42), integer(666) + }; + + auto output = std::format("{}", arr); + REQUIRE(output == "[1, 42, 666]"); + } + + SECTION("parsed array") { + auto ucl = parse("array = [1, 42, 666];").value(); + auto arr = object_cast<array<integer>>(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<integer>(); + auto strm = std::ostringstream(); + strm << arr; + + REQUIRE(strm.str() == "[]"); + } + + SECTION("bare array") { + auto arr = array<integer>{ + integer(1), integer(42), integer(666) + }; + auto strm = std::ostringstream(); + strm << arr; + + REQUIRE(strm.str() == "[1, 42, 666]"); + } + + SECTION("parsed array") { + auto ucl = parse("array = [1, 42, 666];").value(); + auto arr = object_cast<array<integer>>(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; @@ -296,3 +415,65 @@ TEST_CASE("ucl: array: homogeneous cast", "[ucl]") REQUIRE(obj_arr[1] == 42); } +TEST_CASE("array iterator: empty iterator", "[ucl]") +{ + using namespace nihil::ucl; + + auto it = array_iterator<integer>(); + + 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<integer>(); + 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>{ 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>{ 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<integer>(); + REQUIRE_THROWS_AS(it == it2, std::logic_error); + REQUIRE_THROWS_AS(it > it2, std::logic_error); + REQUIRE_THROWS_AS(it - it2, std::logic_error); + } +} diff --git a/nihil.ucl/tests/boolean.cc b/nihil.ucl/tests/boolean.cc index 495071d..f7ef95e 100644 --- a/nihil.ucl/tests/boolean.cc +++ b/nihil.ucl/tests/boolean.cc @@ -27,16 +27,71 @@ TEST_CASE("ucl: boolean: invariants", "[ucl]") static_assert(std::swappable<boolean>); } -TEST_CASE("ucl: boolean: default construct", "[ucl]") +TEST_CASE("ucl: boolean: constructor", "[ucl]") { - auto b = nihil::ucl::boolean(); - REQUIRE(b == false); + 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 value", "[ucl]") +TEST_CASE("ucl: boolean: construct from UCL object", "[ucl]") { - auto b = nihil::ucl::boolean(true); - REQUIRE(b == true); + 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]") @@ -72,57 +127,98 @@ TEST_CASE("ucl: boolean: key()", "[ucl]") REQUIRE(b.key() == ""); } -TEST_CASE("ucl: boolean: operator==", "[ucl]") +TEST_CASE("ucl: boolean: comparison", "[ucl]") { - auto b = nihil::ucl::boolean(true); + using namespace nihil::ucl; - REQUIRE(b == true); - REQUIRE(b == nihil::ucl::boolean(true)); + auto b = boolean(true); - REQUIRE(b != false); - REQUIRE(b != nihil::ucl::boolean(false)); -} + SECTION("operator==") { + REQUIRE(b == true); + REQUIRE(b == boolean(true)); + } -TEST_CASE("ucl: boolean: operator<=>", "[ucl]") -{ - auto b = nihil::ucl::boolean(false); + SECTION("operator!=") { + REQUIRE(b != false); + REQUIRE(b != boolean(false)); + } - REQUIRE(b < true); - REQUIRE(b < nihil::ucl::boolean(true)); + SECTION("operator<") { + REQUIRE(b <= true); + REQUIRE(b <= nihil::ucl::boolean(true)); + } - REQUIRE(b >= false); - REQUIRE(b >= nihil::ucl::boolean(false)); + SECTION("operator>") { + REQUIRE(b > false); + REQUIRE(b > nihil::ucl::boolean(false)); + } } TEST_CASE("ucl: boolean: parse", "[ucl]") { - using namespace std::literals; - - auto err = nihil::ucl::parse("value = true"sv); - REQUIRE(err); + using namespace nihil::ucl; - auto obj = *err; + auto obj = parse("value = true").value(); auto v = obj["value"]; REQUIRE(v.key() == "value"); - REQUIRE(*object_cast<nihil::ucl::boolean>(v) == true); -} - -TEST_CASE("ucl: boolean: emit", "[ucl]") -{ - auto b = nihil::ucl::boolean(true); - auto str = std::format("{}", b); - REQUIRE(str == "true"); + REQUIRE(object_cast<boolean>(v).value() == true); } TEST_CASE("ucl: boolean: parse and emit", "[ucl]") { - auto ucl = nihil::ucl::parse("bool = true;"); - REQUIRE(ucl); + using namespace nihil::ucl; + + auto ucl = parse("bool = true;").value(); auto output = std::string(); - emit(*ucl, nihil::ucl::emitter::configuration, + 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<boolean>(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<boolean>(obj["bool"]).value(); + + auto strm = std::ostringstream(); + strm << i; + + REQUIRE(strm.str() == "true"); + } +} diff --git a/nihil.ucl/tests/integer.cc b/nihil.ucl/tests/integer.cc index 05647fe..6584764 100644 --- a/nihil.ucl/tests/integer.cc +++ b/nihil.ucl/tests/integer.cc @@ -28,16 +28,90 @@ TEST_CASE("ucl: integer: invariants", "[ucl]") static_assert(std::swappable<integer>); } -TEST_CASE("ucl: integer: default construct", "[ucl]") +TEST_CASE("ucl: integer: constructor", "[ucl]") { - auto i = nihil::ucl::integer(); - REQUIRE(i == 0); + 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: construct", "[ucl]") +TEST_CASE("ucl: integer: literal", "[ucl]") { - auto i = nihil::ucl::integer(42); - REQUIRE(i == 42); + 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]") @@ -55,7 +129,9 @@ TEST_CASE("ucl: integer: swap", "[ucl]") TEST_CASE("ucl: integer: value()", "[ucl]") { - auto i = nihil::ucl::integer(42); + using namespace nihil::ucl; + + auto i = 42_ucl; REQUIRE(i.value() == 42); } @@ -63,67 +139,109 @@ TEST_CASE("ucl: integer: key()", "[ucl]") { using namespace nihil::ucl; - auto err = parse("an_int = 42"); - REQUIRE(err); + SECTION("parsed with key") { + auto obj = parse("an_int = 42").value(); + auto i = object_cast<integer>(obj["an_int"]).value(); + REQUIRE(i.key() == "an_int"); + } - auto obj = *err; - REQUIRE(object_cast<integer>(obj["an_int"])->key() == "an_int"); - - auto i = nihil::ucl::integer(42); - REQUIRE(i.key() == ""); + SECTION("bare integer, no key") { + auto i = 42_ucl; + REQUIRE(i.key() == ""); + } } -TEST_CASE("ucl: integer: operator==", "[ucl]") +TEST_CASE("ucl: integer: comparison", "[ucl]") { - auto i = nihil::ucl::integer(42); + using namespace nihil::ucl; - REQUIRE(i == 42); - REQUIRE(i == nihil::ucl::integer(42)); + auto i = 42_ucl; - REQUIRE(i != 1); - REQUIRE(i != nihil::ucl::integer(1)); -} + SECTION("operator==") { + REQUIRE(i == 42); + REQUIRE(i == 42_ucl); + } -TEST_CASE("ucl: integer: operator<=>", "[ucl]") -{ - auto i = nihil::ucl::integer(42); + SECTION("operator!=") { + REQUIRE(i != 1); + REQUIRE(i != 1_ucl); + } - REQUIRE(i < 43); - REQUIRE(i < nihil::ucl::integer(43)); + SECTION("operator<") { + REQUIRE(i < 43); + REQUIRE(i < 43_ucl); + } - REQUIRE(i > 1); - REQUIRE(i > nihil::ucl::integer(1)); + SECTION("operator>") { + REQUIRE(i > 1); + REQUIRE(i > 1_ucl); + } } TEST_CASE("ucl: integer: parse", "[ucl]") { - using namespace std::literals; - - auto err = nihil::ucl::parse("value = 42"sv); - REQUIRE(err); + using namespace nihil::ucl; - auto obj = *err; + auto obj = parse("value = 42").value(); auto v = obj["value"]; REQUIRE(v.key() == "value"); - REQUIRE(object_cast<nihil::ucl::integer>(v) == 42); -} - -TEST_CASE("ucl: integer: emit", "[ucl]") -{ - auto i = nihil::ucl::integer(42); - auto str = std::format("{}", i); - REQUIRE(str == "42"); + REQUIRE(object_cast<integer>(v) == 42); } TEST_CASE("ucl: integer: parse and emit", "[ucl]") { - auto ucl = nihil::ucl::parse("int = 42;"); - REQUIRE(ucl); + using namespace nihil::ucl; + + auto ucl = parse("int = 42;").value(); auto output = std::string(); - emit(*ucl, nihil::ucl::emitter::configuration, - std::back_inserter(output)); + 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<integer>(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<integer>(obj["int"]).value(); + + auto strm = std::ostringstream(); + strm << i; + + REQUIRE(strm.str() == "42"); + } +} diff --git a/nihil.ucl/tests/real.cc b/nihil.ucl/tests/real.cc index be4e213..421917e 100644 --- a/nihil.ucl/tests/real.cc +++ b/nihil.ucl/tests/real.cc @@ -28,17 +28,90 @@ TEST_CASE("ucl: real: invariants", "[ucl]") static_assert(std::swappable<real>); } -TEST_CASE("ucl: real: construct", "[ucl]") +TEST_CASE("ucl: real: constructor", "[ucl]") { - auto obj = nihil::ucl::real(42.1); - REQUIRE_THAT(object_cast<nihil::ucl::real>(obj)->value(), - Catch::Matchers::WithinRel(42.1)); + 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: default construct", "[ucl]") +TEST_CASE("ucl: real: literal", "[ucl]") { - auto i = nihil::ucl::real(); - REQUIRE(i == 0); + 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]") @@ -54,53 +127,122 @@ TEST_CASE("ucl: real: swap", "[ucl]") REQUIRE(r2 == 1.); } -TEST_CASE("ucl: real: operator==", "[ucl]") +TEST_CASE("ucl: real: value()", "[ucl]") { - auto i = nihil::ucl::real(42.5); + 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; - REQUIRE(i == 42.5); - REQUIRE(i == nihil::ucl::real(42.5)); + SECTION("parsed with key") { + auto obj = parse("a_real = 42.5").value(); + auto r = object_cast<real>(obj["a_real"]).value(); + REQUIRE(r.key() == "a_real"); + } - REQUIRE(i != 1); - REQUIRE(i != nihil::ucl::real(1)); + SECTION("bare real, no key") { + auto i = 42.5_ucl; + REQUIRE(i.key() == ""); + } } -TEST_CASE("ucl: real: operator<=>", "[ucl]") +TEST_CASE("ucl: real: comparison", "[ucl]") { - auto i = nihil::ucl::real(42.5); + using namespace nihil::ucl; - REQUIRE(i < 43); - REQUIRE(i < nihil::ucl::real(43)); + auto i = nihil::ucl::real(42.5); - REQUIRE(i > 1); - REQUIRE(i > nihil::ucl::real(1)); + 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 std::literals; - - auto err = nihil::ucl::parse("value = 42.1"sv); - REQUIRE(err); + using namespace nihil::ucl; - auto obj = *err; + auto obj = parse("value = 42.1").value(); auto v = obj["value"]; REQUIRE(v.key() == "value"); - REQUIRE_THAT(object_cast<nihil::ucl::real>(v)->value(), + REQUIRE_THAT(object_cast<real>(v).value().value(), Catch::Matchers::WithinRel(42.1)); } -TEST_CASE("ucl: real: emit", "[ucl]") +TEST_CASE("ucl: real: parse and emit", "[ucl]") { - auto err = nihil::ucl::parse("real = 42.2"); - REQUIRE(err); + using namespace nihil::ucl; - auto obj = *err; + auto ucl = parse("real = 42.2").value(); auto output = std::string(); - emit(obj, nihil::ucl::emitter::configuration, - std::back_inserter(output)); + 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<real>(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<real>(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 index 995e95a..6409b8d 100644 --- a/nihil.ucl/tests/string.cc +++ b/nihil.ucl/tests/string.cc @@ -4,6 +4,7 @@ #include <concepts> #include <list> +#include <sstream> #include <string> #include <vector> @@ -32,58 +33,188 @@ TEST_CASE("ucl: string: invariants", "[ucl]") static_assert(std::same_as<char, std::ranges::range_value_t<string>>); } -TEST_CASE("ucl: string: default construct", "[ucl]") +TEST_CASE("ucl: string: literal", "[ucl]") { - auto str = nihil::ucl::string(); - REQUIRE(str == ""); -} + SECTION("with namespace nihil::ucl::literals") { + using namespace nihil::ucl::literals; -TEST_CASE("ucl: string: construct from string literal", "[ucl]") -{ - auto str = nihil::ucl::string("testing"); - REQUIRE(str == "testing"); -} + auto s = "testing"_ucl; + REQUIRE(s.type() == nihil::ucl::object_type::string); + REQUIRE(s == "testing"); + } -TEST_CASE("ucl: string: construct from std::string", "[ucl]") -{ - auto str = nihil::ucl::string(std::string("testing")); - REQUIRE(str == "testing"); -} + SECTION("with namespace nihil::literals") { + using namespace nihil::literals; -TEST_CASE("ucl: string: construct from std::string_view", "[ucl]") -{ - auto str = nihil::ucl::string(std::string_view("testing")); - REQUIRE(str == "testing"); + auto s = "testing"_ucl; + REQUIRE(s.type() == nihil::ucl::object_type::string); + REQUIRE(s == "testing"); + } } -TEST_CASE("ucl: string: construct from contiguous range", "[ucl]") +TEST_CASE("ucl: string: construct", "[ucl]") { - auto s = std::vector{'t', 'e', 's', 't', 'i', 'n', 'g'}; - auto str = nihil::ucl::string(std::from_range, s); - REQUIRE(str == "testing"); -} + using namespace nihil::ucl; + using namespace std::literals; -TEST_CASE("ucl: string: construct from non-contiguous range", "[ucl]") -{ - auto s = std::list{'t', 'e', 's', 't', 'i', 'n', 'g'}; - auto str = nihil::ucl::string(std::from_range, s); - REQUIRE(str == "testing"); + 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 contiguous iterator", "[ucl]") +TEST_CASE("ucl: string: construct from UCL object", "[ucl]") { - auto s = std::vector{'t', 'e', 's', 't', 'i', 'n', 'g'}; - auto str = nihil::ucl::string(std::ranges::begin(s), - std::ranges::end(s)); - REQUIRE(str == "testing"); + 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: construct from non-contiguous iterator", "[ucl]") +TEST_CASE("ucl: string: make_string", "[ucl]") { - auto s = std::list{'t', 'e', 's', 't', 'i', 'n', 'g'}; - auto str = nihil::ucl::string(std::ranges::begin(s), - std::ranges::end(s)); - REQUIRE(str == "testing"); + 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]") @@ -101,7 +232,9 @@ TEST_CASE("ucl: string: swap", "[ucl]") TEST_CASE("ucl: string: value()", "[ucl]") { - auto s = nihil::ucl::string("te\"st"); + using namespace nihil::ucl; + + auto s = string("te\"st"); REQUIRE(s.value() == "te\"st"); } @@ -115,7 +248,7 @@ TEST_CASE("ucl: string: key()", "[ucl]") auto obj = *err; REQUIRE(object_cast<string>(obj["a_string"])->key() == "a_string"); - auto s = nihil::ucl::string("test"); + auto s = string("test"); REQUIRE(s.key() == ""); } @@ -135,86 +268,148 @@ TEST_CASE("ucl: string: empty", "[ucl]") REQUIRE(string("test").empty() == false); } -TEST_CASE("ucl: string: iterator", "[ucl]") +TEST_CASE("ucl: string: iterate", "[ucl]") { - auto str = nihil::ucl::string("test"); - - auto begin = std::ranges::begin(str); - static_assert(std::contiguous_iterator<decltype(begin)>); + using namespace nihil::ucl; - auto end = std::ranges::end(str); - static_assert(std::sentinel_for<decltype(end), decltype(begin)>); + auto str = "test"_ucl; - REQUIRE(*begin == 't'); - ++begin; - REQUIRE(*begin == 'e'); - ++begin; - REQUIRE(*begin == 's'); - ++begin; - REQUIRE(*begin == 't'); - ++begin; + SECTION("as iterator pair") { + auto begin = str.begin(); + static_assert(std::contiguous_iterator<decltype(begin)>); - REQUIRE(begin == end); -} + auto end = str.end(); + static_assert(std::sentinel_for<decltype(end), + decltype(begin)>); -TEST_CASE("ucl: string: operator==", "[ucl]") -{ - auto str = nihil::ucl::string("testing"); + REQUIRE(*begin == 't'); + ++begin; + REQUIRE(*begin == 'e'); + ++begin; + REQUIRE(*begin == 's'); + ++begin; + REQUIRE(*begin == 't'); + ++begin; - REQUIRE(str == nihil::ucl::string("testing")); - REQUIRE(str == std::string_view("testing")); - REQUIRE(str == std::string("testing")); - REQUIRE(str == "testing"); + REQUIRE(begin == end); + } - REQUIRE(str != nihil::ucl::string("test")); - REQUIRE(str != std::string_view("test")); - REQUIRE(str != std::string("test")); - REQUIRE(str != "test"); + SECTION("as range") { + auto s = std::string(std::from_range, str); + REQUIRE(s == "test"); + } } -TEST_CASE("ucl: string: operator<=>", "[ucl]") +TEST_CASE("ucl: string: comparison", "[ucl]") { - auto str = nihil::ucl::string("testing"); - - REQUIRE(str < nihil::ucl::string("zzz")); - REQUIRE(str < std::string_view("zzz")); - REQUIRE(str < std::string("zzz")); - REQUIRE(str < "zzz"); + using namespace nihil::ucl; - REQUIRE(str > nihil::ucl::string("aaa")); - REQUIRE(str > std::string_view("aaa")); - REQUIRE(str > std::string("aaa")); - REQUIRE(str > "aaa"); + 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 std::literals; + using namespace nihil::ucl; - auto err = nihil::ucl::parse("value = \"te\\\"st\""sv); - REQUIRE(err); + auto obj = parse("value = \"te\\\"st\"").value(); - auto obj = *err; auto v = obj["value"]; REQUIRE(v.key() == "value"); - REQUIRE(object_cast<nihil::ucl::string>(v) == "te\"st"); + REQUIRE(object_cast<nihil::ucl::string>(v).value() == "te\"st"); } TEST_CASE("ucl: string: emit", "[ucl]") { - auto s = nihil::ucl::string("te\"st"); - auto str = std::format("{}", s); - REQUIRE(str == "\"te\\\"st\""); -} + using namespace nihil::ucl; -TEST_CASE("ucl: string: parse and emit", "[ucl]") -{ - auto ucl = nihil::ucl::parse("str = \"te\\\"st\";"); - REQUIRE(ucl); + auto ucl = parse("str = \"te\\\"st\";").value(); auto output = std::string(); - emit(*ucl, nihil::ucl::emitter::configuration, - std::back_inserter(output)); + 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<string>(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<string>(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 index a008aa3..7d9cad7 100644 --- a/nihil.ucl/type.cc +++ b/nihil.ucl/type.cc @@ -39,24 +39,24 @@ auto str(object_type type) -> std::string_view { } } -type_mismatch::type_mismatch( - object_type expected_type, object_type actual_type) - : error(std::format("UCL type mismatch: expected type '{}' " - "!= actual type '{}'", - str(expected_type), str(actual_type))) - , _expected_type(expected_type) - , _actual_type(actual_type) +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._expected_type; + return self.m_expected_type; } auto type_mismatch::actual_type(this type_mismatch const &self) -> object_type { - return self._actual_type; + return self.m_actual_type; } } // namespace nihil::ucl diff --git a/nihil.ucl/type.ccm b/nihil.ucl/type.ccm index 088d196..cd98c01 100644 --- a/nihil.ucl/type.ccm +++ b/nihil.ucl/type.ccm @@ -6,13 +6,14 @@ module; #include <concepts> #include <format> +#include <stdexcept> #include <string> #include <ucl.h> export module nihil.ucl:type; -import :error; +import nihil; namespace nihil::ucl { @@ -50,8 +51,8 @@ export struct type_mismatch : error { auto actual_type(this type_mismatch const &self) -> object_type; private: - object_type _expected_type; - object_type _actual_type; + object_type m_expected_type; + object_type m_actual_type; }; } // namespace nihil::ucl |
