aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--nihil.ucl/CMakeLists.txt4
-rw-r--r--nihil.ucl/array.ccm251
-rw-r--r--nihil.ucl/boolean.cc65
-rw-r--r--nihil.ucl/boolean.ccm55
-rw-r--r--nihil.ucl/emit.ccm4
-rw-r--r--nihil.ucl/errc.cc51
-rw-r--r--nihil.ucl/errc.ccm33
-rw-r--r--nihil.ucl/error.cc19
-rw-r--r--nihil.ucl/error.ccm22
-rw-r--r--nihil.ucl/integer.cc68
-rw-r--r--nihil.ucl/integer.ccm89
-rw-r--r--nihil.ucl/map.ccm95
-rw-r--r--nihil.ucl/nihil.ucl.ccm2
-rw-r--r--nihil.ucl/object.cc38
-rw-r--r--nihil.ucl/object.ccm24
-rw-r--r--nihil.ucl/parser.cc4
-rw-r--r--nihil.ucl/parser.ccm13
-rw-r--r--nihil.ucl/real.cc65
-rw-r--r--nihil.ucl/real.ccm85
-rw-r--r--nihil.ucl/string.cc72
-rw-r--r--nihil.ucl/string.ccm196
-rw-r--r--nihil.ucl/tests/array.cc251
-rw-r--r--nihil.ucl/tests/boolean.cc168
-rw-r--r--nihil.ucl/tests/integer.cc208
-rw-r--r--nihil.ucl/tests/real.cc204
-rw-r--r--nihil.ucl/tests/string.cc383
-rw-r--r--nihil.ucl/type.cc18
-rw-r--r--nihil.ucl/type.ccm7
-rw-r--r--nihil/CMakeLists.txt1
-rw-r--r--nihil/error.cc42
-rw-r--r--nihil/error.ccm43
-rw-r--r--nihil/exec.ccm1
-rw-r--r--nihil/fd.cc9
-rw-r--r--nihil/fd.ccm9
-rw-r--r--nihil/generic_error.ccm35
-rw-r--r--nihil/nihil.ccm1
-rw-r--r--nihil/tabulate.ccm9
-rw-r--r--nihil/tests/CMakeLists.txt1
-rw-r--r--nihil/tests/error.cc99
-rw-r--r--nihil/tests/fd.cc3
-rw-r--r--nihil/tests/generic_error.cc32
-rw-r--r--nihil/usage_error.ccm9
42 files changed, 2071 insertions, 717 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
diff --git a/nihil/CMakeLists.txt b/nihil/CMakeLists.txt
index fea785b..cb0cd21 100644
--- a/nihil/CMakeLists.txt
+++ b/nihil/CMakeLists.txt
@@ -14,7 +14,6 @@ target_sources(nihil
find_in_path.ccm
format_filesystem.ccm
generator.ccm
- generic_error.ccm
getenv.ccm
guard.ccm
match.ccm
diff --git a/nihil/error.cc b/nihil/error.cc
index dba3021..c2cfd49 100644
--- a/nihil/error.cc
+++ b/nihil/error.cc
@@ -14,10 +14,25 @@ module nihil;
namespace nihil {
+auto to_string(error const &self) -> std::string
+{
+ auto ret = self.str();
+
+ auto cause = self.cause();
+ while (cause) {
+ ret += ": " + cause->str();
+ cause = cause->cause();
+ }
+
+ return ret;
+}
+
error::error()
{
}
+error::~error() = default;
+
error::error(std::string_view what, error cause)
: m_error(std::string(what))
, m_cause(std::make_shared<error>(std::move(cause)))
@@ -55,10 +70,10 @@ error::error(error &&) noexcept = default;
auto error::operator=(this error &, error const &) -> error & = default;
auto error::operator=(this error &, error &&) noexcept -> error & = default;
-auto error::cause(this error const &self) -> std::optional<error>
+auto error::cause(this error const &self) -> std::shared_ptr<error>
{
if (self.m_cause)
- return *self.m_cause;
+ return self.m_cause;
return {};
}
@@ -88,19 +103,6 @@ auto error::str(this error const &self) -> std::string
};
}
-auto error::what(this error const &self) -> std::string
-{
- auto ret = self.str();
-
- auto cause = self.m_cause;
- while (cause) {
- ret += ": " + cause->str();
- cause = cause->m_cause;
- }
-
- return ret;
-}
-
auto error::code(this error const &self) -> std::optional<std::error_code>
{
auto const *code = std::get_if<std::error_code>(&self.m_error);
@@ -118,6 +120,14 @@ auto error::condition(this error const &self)
return {};
}
+auto error::what() const noexcept -> char const *
+{
+ if (!m_what)
+ m_what = to_string(*this);
+
+ return m_what->c_str();
+}
+
auto operator==(error const &lhs, error const &rhs) -> bool
{
return lhs.m_error == rhs.m_error;
@@ -141,7 +151,7 @@ auto operator==(error const &lhs, std::error_condition const &rhs) -> bool
auto operator<<(std::ostream &strm, error const &e) -> std::ostream &
{
- return strm << e.what();
+ return strm << to_string(e);
}
} // namespace nihil
diff --git a/nihil/error.ccm b/nihil/error.ccm
index 7576480..7d220e1 100644
--- a/nihil/error.ccm
+++ b/nihil/error.ccm
@@ -19,9 +19,12 @@ module;
* Calling .str() will format the entire stack starting at that error,
* for example: "failed to open /etc/somefile: No such file or directory".
*
- * Errors may be copied, moved and thrown, although throwing errors is probably
- * not very useful since there's no way to distinguish different errors.
+ * Errors may be moved and (relatively) cheaply copied, since the cause
+ * chain is refcounted.
*
+ * error derives from std::exception, so it may be thrown and caught and
+ * provides a useful what(). When throwing errors, creating a derived
+ * error will make it easier to distinguish errors when catching them.
*/
#include <iosfwd>
@@ -45,10 +48,13 @@ using error_t = std::variant<
std::error_condition
>;
-export struct error {
+export struct error : std::exception {
// Create an empty error, representing success.
error();
+ // Destroy an error.
+ virtual ~error();
+
// Create an error from a freeform string.
error(std::string_view what, error cause);
explicit error(std::string_view what);
@@ -71,23 +77,23 @@ export struct error {
// Create an error from an std::error_code enum.
error(auto errc, error cause)
requires(std::is_error_code_enum<decltype(errc)>::value)
- : error(std::move(cause), std::make_error_code(errc))
+ : error(make_error_code(errc), std::move(cause))
{}
explicit error(auto errc)
requires(std::is_error_code_enum<decltype(errc)>::value)
- : error(std::make_error_code(errc))
+ : error(make_error_code(errc))
{}
// Create an error from an std::error_condition enum.
error(auto errc, error cause)
requires(std::is_error_condition_enum<decltype(errc)>::value)
- : error(std::move(cause), std::make_error_condition(errc))
+ : error(make_error_condition(errc), std::move(cause))
{}
explicit error(auto errc)
requires(std::is_error_condition_enum<decltype(errc)>::value)
- : error(std::make_error_condition(errc))
+ : error(make_error_condition(errc))
{}
error(error const &);
@@ -97,16 +103,13 @@ export struct error {
auto operator=(this error &, error &&) noexcept -> error &;
// Return the cause of this error.
- [[nodiscard]] auto cause(this error const &) -> std::optional<error>;
+ [[nodiscard]] auto cause(this error const &) -> std::shared_ptr<error>;
// Return the root cause of this error, which may be this object.
// For errors caused by an OS error, this will typically be the
// error_code error.
[[nodiscard]] auto root_cause(this error const &) -> error const &;
- // Format this error and its cause(s) as a string.
- [[nodiscard]] auto what(this error const &) -> std::string;
-
// Format this error as a string.
[[nodiscard]] auto str(this error const &) -> std::string;
@@ -118,15 +121,29 @@ export struct error {
[[nodiscard]] auto condition(this error const &)
-> std::optional<std::error_condition>;
+ auto what() const noexcept -> char const * final;
+
private:
friend auto operator==(error const &, error const &) -> bool;
friend auto operator<=>(error const &, error const &)
-> std::strong_ordering;
- error_t m_error = std::make_error_code(std::errc());
+ // This error.
+ error_t m_error = make_error_code(std::errc());
+
+ // The cause of this error, if any.
std::shared_ptr<error> m_cause;
+
+ // For std::exception::what(), we need to keep the string valid
+ // until we're destroyed.
+ mutable std::optional<std::string> m_what;
};
+/*
+ * Format an error and its cause(s) as a string.
+ */
+export [[nodiscard]] auto to_string(error const &) -> std::string;
+
// Compare an error to another error. This only compares the error itself,
// not any nested causes.
export [[nodiscard]] auto operator==(error const &, error const &)
@@ -177,6 +194,6 @@ struct std::formatter<nihil::error, char>
auto format(nihil::error const &e, FormatContext &ctx) const
-> FormatContext::iterator
{
- return std::ranges::copy(e.what(), ctx.out()).out;
+ return std::ranges::copy(to_string(e), ctx.out()).out;
}
};
diff --git a/nihil/exec.ccm b/nihil/exec.ccm
index 8fcbc14..5c72dfe 100644
--- a/nihil/exec.ccm
+++ b/nihil/exec.ccm
@@ -17,7 +17,6 @@ import :argv;
import :error;
import :fd;
import :find_in_path;
-import :generic_error;
namespace nihil {
diff --git a/nihil/fd.cc b/nihil/fd.cc
index 3722d2e..066056f 100644
--- a/nihil/fd.cc
+++ b/nihil/fd.cc
@@ -17,11 +17,6 @@ module nihil;
namespace nihil {
-fd_logic_error::fd_logic_error(std::string what)
- : std::logic_error(std::move(what))
-{
-}
-
fd::fd() noexcept = default;
fd::fd(int fileno) noexcept
@@ -67,14 +62,14 @@ auto fd::get(this fd const &self) -> int
{
if (self)
return self.m_fileno;
- throw fd_logic_error("Attempt to call get() on invalid fd");
+ throw std::logic_error("Attempt to call get() on invalid fd");
}
auto fd::release(this fd &&self) -> int
{
if (self)
return std::exchange(self.m_fileno, invalid_fileno);
- throw fd_logic_error("Attempt to release an invalid fd");
+ throw std::logic_error("Attempt to release an invalid fd");
}
auto dup(fd const &self) -> std::expected<fd, error>
diff --git a/nihil/fd.ccm b/nihil/fd.ccm
index 93a05f5..3503833 100644
--- a/nihil/fd.ccm
+++ b/nihil/fd.ccm
@@ -14,19 +14,10 @@ module;
export module nihil:fd;
import :error;
-import :generic_error;
namespace nihil {
/*
- * Exception thrown when an internal fd error occurs. This is not supposed
- * to be caught, since it indicates an internal logic error in the caller.
- */
-export struct fd_logic_error final : std::logic_error {
- fd_logic_error(std::string what);
-};
-
-/*
* fd: a file descriptor.
*/
diff --git a/nihil/generic_error.ccm b/nihil/generic_error.ccm
deleted file mode 100644
index 4322236..0000000
--- a/nihil/generic_error.ccm
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * This source code is released into the public domain.
- */
-
-module;
-
-#include <format>
-#include <stdexcept>
-
-export module nihil:generic_error;
-
-import :format_filesystem;
-
-namespace nihil {
-
-/*
- * generic_error is the base class that all other exceptions derive from.
- * It is an std::runtime_error, and what() should always be informative.
- */
-
-export struct generic_error : std::runtime_error {
- generic_error(char const *what)
- : std::runtime_error(what)
- {}
-
- generic_error(std::string_view what)
- : std::runtime_error(std::string(what).c_str())
- {}
-
- generic_error(std::string const &what)
- : std::runtime_error(what.c_str())
- {}
-};
-
-} // namespace nihil
diff --git a/nihil/nihil.ccm b/nihil/nihil.ccm
index 2a18b6e..61e096c 100644
--- a/nihil/nihil.ccm
+++ b/nihil/nihil.ccm
@@ -16,7 +16,6 @@ export import :fd;
export import :find_in_path;
export import :format_filesystem;
export import :generator;
-export import :generic_error;
export import :getenv;
export import :guard;
export import :match;
diff --git a/nihil/tabulate.ccm b/nihil/tabulate.ccm
index 73e251d..9e2303b 100644
--- a/nihil/tabulate.ccm
+++ b/nihil/tabulate.ccm
@@ -14,7 +14,7 @@ module;
export module nihil:tabulate;
import :ctype;
-import :generic_error;
+import :error;
namespace nihil {
@@ -38,8 +38,11 @@ namespace nihil {
*/
// Exception thrown when a table spec is invalid.
-export struct table_spec_error : generic_error {
- table_spec_error(std::string what) : generic_error(std::move(what)) {}
+export struct table_spec_error : error {
+ table_spec_error(std::string_view what)
+ : error(what)
+ {
+ }
};
/*
diff --git a/nihil/tests/CMakeLists.txt b/nihil/tests/CMakeLists.txt
index e3e7c06..1c4635f 100644
--- a/nihil/tests/CMakeLists.txt
+++ b/nihil/tests/CMakeLists.txt
@@ -6,7 +6,6 @@ add_executable(nihil.test
error.cc
fd.cc
generator.cc
- generic_error.cc
getenv.cc
guard.cc
monad.cc
diff --git a/nihil/tests/error.cc b/nihil/tests/error.cc
index 7b079fd..31124e3 100644
--- a/nihil/tests/error.cc
+++ b/nihil/tests/error.cc
@@ -24,67 +24,77 @@ TEST_CASE("error: invariants", "[nihil]")
TEST_CASE("error: construct from string", "[nihil]")
{
- auto e = nihil::error("an error");
- REQUIRE(e.str() == e.what());
- REQUIRE(e.what() == "an error");
- REQUIRE(std::format("{}", e) == e.what());
+ using namespace nihil;
+
+ auto e = error("an error");
+ REQUIRE(e.str() == to_string(e));
+ REQUIRE(to_string(e) == "an error");
+ REQUIRE(std::format("{}", e) == to_string(e));
}
TEST_CASE("error: construct from std::error_condition", "[nihil]")
{
+ using namespace nihil;
+
auto code = std::make_error_condition(std::errc::invalid_argument);
- auto e = nihil::error(code);
+ auto e = error(code);
- REQUIRE(e.cause().has_value() == false);
+ REQUIRE(!e.cause());
REQUIRE(e.code().has_value() == false);
REQUIRE(e.condition().has_value() == true);
REQUIRE(e == std::errc::invalid_argument);
REQUIRE(e != std::errc::no_such_file_or_directory);
- REQUIRE(e.str() == e.what());
- REQUIRE(e.what() == std::strerror(EINVAL));
- REQUIRE(std::format("{}", e) == e.what());
+ REQUIRE(e.str() == to_string(e));
+ REQUIRE(to_string(e) == std::strerror(EINVAL));
+ REQUIRE(std::format("{}", e) == to_string(e));
}
TEST_CASE("error: construct from std::errc", "[nihil]")
{
- auto e = nihil::error(std::errc::invalid_argument);
+ using namespace nihil;
- REQUIRE(e.cause().has_value() == false);
+ auto e = error(std::errc::invalid_argument);
+
+ REQUIRE(!e.cause());
REQUIRE(e.code().has_value() == false);
REQUIRE(e.condition().has_value() == true);
REQUIRE(e == std::errc::invalid_argument);
REQUIRE(e != std::errc::no_such_file_or_directory);
- REQUIRE(e.str() == e.what());
- REQUIRE(e.what() == std::strerror(EINVAL));
- REQUIRE(std::format("{}", e) == e.what());
+ REQUIRE(e.str() == to_string(e));
+ REQUIRE(to_string(e) == std::strerror(EINVAL));
+ REQUIRE(std::format("{}", e) == to_string(e));
}
TEST_CASE("error: compound error", "[nihil]")
{
using namespace std::literals;
+ using namespace nihil;
- auto e = nihil::error("cannot open file",
- nihil::error(std::errc::no_such_file_or_directory));
+ auto e = error("cannot open file",
+ error(std::errc::no_such_file_or_directory));
- REQUIRE(e.cause().has_value() == true);
+ REQUIRE(e.cause());
REQUIRE(e.code().has_value() == false);
REQUIRE(e.condition().has_value() == false);
- REQUIRE(e.cause() == std::errc::no_such_file_or_directory);
+ REQUIRE(*e.cause() == std::errc::no_such_file_or_directory);
REQUIRE(e.str() == "cannot open file");
- REQUIRE(e.what() == ("cannot open file: "s + std::strerror(ENOENT)));
- REQUIRE(std::format("{}", e) == e.what());
+ REQUIRE(to_string(e) == ("cannot open file: "s +
+ std::strerror(ENOENT)));
+ REQUIRE(std::format("{}", e) == to_string(e));
}
TEST_CASE("error: operator== with strings", "[nihil]")
{
- auto e1 = nihil::error("error");
- auto e2 = nihil::error("error");
- auto e3 = nihil::error("an error");
+ using namespace nihil;
+
+ auto e1 = error("error");
+ auto e2 = error("error");
+ auto e3 = error("an error");
REQUIRE(e1 == e2);
REQUIRE(e1 != e3);
@@ -92,25 +102,31 @@ TEST_CASE("error: operator== with strings", "[nihil]")
TEST_CASE("error: operator< with strings", "[nihil]")
{
- auto e1 = nihil::error("aaa");
- auto e2 = nihil::error("zzz");
+ using namespace nihil;
+
+ auto e1 = error("aaa");
+ auto e2 = error("zzz");
REQUIRE(e1 < e2);
}
TEST_CASE("error: operator== with a cause", "[nihil]")
{
- auto e1 = nihil::error("error", nihil::error("cause 1"));
- auto e2 = nihil::error("error", nihil::error("cause 2"));
+ using namespace nihil;
+
+ auto e1 = error("error", error("cause 1"));
+ auto e2 = error("error", error("cause 2"));
REQUIRE(e1 == e2);
}
TEST_CASE("error: operator== with error_conditions", "[nihil]")
{
- auto e1 = nihil::error(std::errc::invalid_argument);
- auto e2 = nihil::error(std::errc::invalid_argument);
- auto e3 = nihil::error(std::errc::permission_denied);
+ using namespace nihil;
+
+ auto e1 = error(std::errc::invalid_argument);
+ auto e2 = error(std::errc::invalid_argument);
+ auto e3 = error(std::errc::permission_denied);
REQUIRE(e1 == e2);
REQUIRE(e1 != e3);
@@ -118,19 +134,36 @@ TEST_CASE("error: operator== with error_conditions", "[nihil]")
TEST_CASE("error: std::format with string", "[nihil]")
{
- auto err = nihil::error("an error");
+ using namespace nihil;
+
+ auto err = error("an error");
REQUIRE(std::format("{}", err) == "an error");
}
TEST_CASE("error: std::format with std::errc", "[nihil]")
{
- auto err = nihil::error(std::errc::invalid_argument);
+ using namespace nihil;
+
+ auto err = error(std::errc::invalid_argument);
REQUIRE(std::format("{}", err) == std::strerror(EINVAL));
}
TEST_CASE("error: std::format with cause", "[nihil]")
{
- auto err = nihil::error("an error", std::errc::invalid_argument);
+ using namespace nihil;
+
+ auto err = error("an error", std::errc::invalid_argument);
REQUIRE(std::format("{}", err) == "an error: Invalid argument");
}
+TEST_CASE("error: throw and catch", "[nihil]")
+{
+ using namespace std::literals;
+ using namespace nihil;
+
+ try {
+ throw error("oh no", error(std::errc::invalid_argument));
+ } catch (std::exception const &exc) {
+ REQUIRE(exc.what() == "oh no: Invalid argument"s);
+ }
+}
diff --git a/nihil/tests/fd.cc b/nihil/tests/fd.cc
index d8acd35..59054fa 100644
--- a/nihil/tests/fd.cc
+++ b/nihil/tests/fd.cc
@@ -3,6 +3,7 @@
*/
#include <span>
+#include <stdexcept>
#include <stdio.h>
#include <fcntl.h>
@@ -27,7 +28,7 @@ TEST_CASE("fd: construct empty", "[fd]") {
nihil::fd fd;
REQUIRE(!fd);
- REQUIRE_THROWS_AS(fd.get(), nihil::fd_logic_error);
+ REQUIRE_THROWS_AS(fd.get(), std::logic_error);
}
TEST_CASE("fd: construct from fd", "[fd]") {
diff --git a/nihil/tests/generic_error.cc b/nihil/tests/generic_error.cc
deleted file mode 100644
index ee3eccd..0000000
--- a/nihil/tests/generic_error.cc
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * This source code is released into the public domain.
- */
-
-#include <catch2/catch_test_macros.hpp>
-
-import nihil;
-
-TEST_CASE("generic_error: basic", "[generic_error]") {
- using namespace std::literals;
-
- // C string
- try {
- throw nihil::generic_error("test error");
- } catch (nihil::generic_error const &exc) {
- REQUIRE(exc.what() == "test error"sv);
- }
-
- // std::string
- try {
- throw nihil::generic_error("test error"s);
- } catch (nihil::generic_error const &exc) {
- REQUIRE(exc.what() == "test error"sv);
- }
-
- // std::string_view
- try {
- throw nihil::generic_error("test error"sv);
- } catch (nihil::generic_error const &exc) {
- REQUIRE(exc.what() == "test error"sv);
- }
-}
diff --git a/nihil/usage_error.ccm b/nihil/usage_error.ccm
index 41de29c..abbd6f0 100644
--- a/nihil/usage_error.ccm
+++ b/nihil/usage_error.ccm
@@ -4,20 +4,19 @@
module;
-#include <format>
-#include <utility>
+#include <string>
export module nihil:usage_error;
-import :generic_error;
+import :error;
namespace nihil {
/*
* Exception thrown to indicate invalid command-line arguments.
*/
-export struct usage_error : generic_error {
- usage_error(std::string what) : generic_error(std::move(what)) {}
+export struct usage_error : error {
+ usage_error(std::string_view what) : error(what) {}
};
} // namespace nihil