diff options
| -rw-r--r-- | nihil.ucl/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | nihil.ucl/array.ccm | 4 | ||||
| -rw-r--r-- | nihil.ucl/nihil.ucl.ccm | 3 | ||||
| -rw-r--r-- | nihil.ucl/object.ccm | 75 | ||||
| -rw-r--r-- | nihil.ucl/object_cast.ccm | 81 | ||||
| -rw-r--r-- | nihil.ucl/tests/array.cc | 123 | ||||
| -rw-r--r-- | nihil.ucl/type.ccm | 93 |
7 files changed, 293 insertions, 88 deletions
diff --git a/nihil.ucl/CMakeLists.txt b/nihil.ucl/CMakeLists.txt index f01fb25..9c6b3f3 100644 --- a/nihil.ucl/CMakeLists.txt +++ b/nihil.ucl/CMakeLists.txt @@ -10,7 +10,9 @@ target_sources(nihil.ucl PUBLIC emit.ccm error.ccm object.ccm + object_cast.ccm parser.ccm + type.ccm array.ccm boolean.ccm diff --git a/nihil.ucl/array.ccm b/nihil.ucl/array.ccm index 26cd9b9..7488bde 100644 --- a/nihil.ucl/array.ccm +++ b/nihil.ucl/array.ccm @@ -150,9 +150,7 @@ auto operator-(array_iterator<T> const &lhs, return copy; } -static_assert(std::random_access_iterator<array_iterator<object>>); - -export template<datatype T> +export template<datatype T = object> struct array final : object { inline static constexpr object_type ucl_type = object_type::array; diff --git a/nihil.ucl/nihil.ucl.ccm b/nihil.ucl/nihil.ucl.ccm index d0924d8..909fe18 100644 --- a/nihil.ucl/nihil.ucl.ccm +++ b/nihil.ucl/nihil.ucl.ccm @@ -5,9 +5,12 @@ module; export module nihil.ucl; + export import :emit; export import :object; +export import :object_cast; export import :parser; +export import :type; export import :array; export import :boolean; diff --git a/nihil.ucl/object.ccm b/nihil.ucl/object.ccm index 43f36fe..7286301 100644 --- a/nihil.ucl/object.ccm +++ b/nihil.ucl/object.ccm @@ -23,6 +23,7 @@ export module nihil.ucl:object; import nihil; import :error; +import :type; namespace nihil::ucl { @@ -32,26 +33,13 @@ export struct parser; * The basic object type. */ -enum struct object_type { - object = UCL_OBJECT, - array = UCL_ARRAY, - integer = UCL_INT, - real = UCL_FLOAT, - string = UCL_STRING, - boolean = UCL_BOOLEAN, - time = UCL_TIME, - userdata = UCL_USERDATA, - null = UCL_NULL, -}; - -template<typename T> -concept datatype = requires(T o) { - { T::ucl_type } -> std::convertible_to<object_type>; -}; - export struct object { inline static constexpr object_type ucl_type = object_type::object; + // Create an object from an existing ucl_object_t. We assume the + // object has already been referenced. + object(::ucl_object_t *object) : _object(object) {} + // Free our object on destruction. virtual ~object() { if (_object) @@ -100,28 +88,8 @@ export struct object { // Return the type of this object. auto type(this object const &self) -> object_type { - switch (ucl_object_type(self.get_ucl_object())) { - case UCL_OBJECT: - return object_type::object; - case UCL_ARRAY: - return object_type::array; - case UCL_INT: - return object_type::integer; - case UCL_FLOAT: - return object_type::real; - case UCL_STRING: - return object_type::string; - case UCL_BOOLEAN: - return object_type::boolean; - case UCL_TIME: - return object_type::time; - case UCL_USERDATA: - return object_type::userdata; - case UCL_NULL: - return object_type::null; - default: - std::abort(); - } + auto utype = ::ucl_object_type(self.get_ucl_object()); + return static_cast<object_type>(utype); } // Return the underlying object. @@ -156,10 +124,6 @@ export struct object { } protected: - // Create an object from an existing ucl_object_t. We assume the - // object has already been referenced. - object(::ucl_object_t *object) : _object(object) {} - // The object we're wrapping. ::ucl_object_t *_object = nullptr; @@ -274,29 +238,4 @@ export auto end(object const &) -> iterator::sentinel return {}; } -/*********************************************************************** - * Value access by object_cast. - */ - -// Exception thrown when object_cast fails. -export struct bad_object_cast : error { - bad_object_cast() - : error("bad object_cast<>: object does not match target type") - {} -}; - -//export template<typename To> -////auto object_cast(object const &from) -> To = delete; - -export template<datatype To> -auto object_cast(object const &from) -> To -{ - if (from.type() != To::ucl_type) - throw bad_object_cast(); - - auto const *uobj = from.get_ucl_object(); - auto *refptr = ::ucl_object_ref(uobj); - return To(refptr); -} - } // namespace nihil::ucl diff --git a/nihil.ucl/object_cast.ccm b/nihil.ucl/object_cast.ccm new file mode 100644 index 0000000..7291960 --- /dev/null +++ b/nihil.ucl/object_cast.ccm @@ -0,0 +1,81 @@ +/* + * This source code is released into the public domain. + */ + +module; + +#include <cstdlib> + +#include <ucl.h> + +export module nihil.ucl:object_cast; + +import :type; +import :object; +import :array; + +namespace nihil::ucl { + +/* + * Ensure a UCL object is convertible to another type. Throws type_mismatch + * if not. + */ + +// Implementation for basic types. +template<datatype To> +struct convert_check +{ + auto check(::ucl_object_t const *from) -> void + { + auto from_type = static_cast<object_type>(::ucl_object_type(from)); + auto to_type = To::ucl_type; + + // Converting from anything to object is permitted. + if (to_type == object_type::object) + return; + + // Converting between two equal types is permitted. + if (from_type == to_type) + return; + + // Otherwise, this is an error. + throw type_mismatch(to_type, from_type); + } +}; + +// Implementation for array. +template<typename T> +struct convert_check<array<T>> +{ + auto check(::ucl_object_t const *from) -> void + { + using To = array<T>; + auto from_type = static_cast<object_type>(::ucl_object_type(from)); + auto to_type = To::ucl_type; + + // If the source type is not an array, this is an error. + if (from_type != object_type::array) + throw type_mismatch(to_type, from_type); + + for (std::size_t i = 0, size = ::ucl_array_size(from); + i < size; ++i) { + auto const *arr_obj = ::ucl_array_find_index(from, i); + convert_check<typename To::value_type>{}.check(arr_obj); + } + } +}; + +/* + * Convert a UCL object to another type. + */ +export template<datatype To> +auto object_cast(object const &from) -> To +{ + convert_check<To>{}.check(from.get_ucl_object()); + + auto const *uobj = from.get_ucl_object(); + auto *refptr = ::ucl_object_ref(uobj); + return To(refptr); +} + +} // namespace nihil::ucl diff --git a/nihil.ucl/tests/array.cc b/nihil.ucl/tests/array.cc index 60cb61d..220564d 100644 --- a/nihil.ucl/tests/array.cc +++ b/nihil.ucl/tests/array.cc @@ -12,18 +12,23 @@ import nihil.ucl; TEST_CASE("ucl: array: construct", "[ucl]") { - auto arr = nihil::ucl::array<nihil::ucl::integer>(); + using namespace nihil::ucl; + + auto arr = array<integer>(); REQUIRE(arr.size() == 0); + REQUIRE(str(arr.type()) == "array"); } TEST_CASE("ucl: array: push_back", "[ucl]") { - auto arr = nihil::ucl::array<nihil::ucl::integer>(); + using namespace nihil::ucl; + + auto arr = array<integer>(); REQUIRE(arr.size() == 0); - arr.push_back(nihil::ucl::integer(1)); - arr.push_back(nihil::ucl::integer(42)); - arr.push_back(nihil::ucl::integer(666)); + arr.push_back(integer(1)); + arr.push_back(integer(42)); + arr.push_back(integer(666)); REQUIRE(arr.size() == 3); REQUIRE(arr[0].value() == 1); @@ -34,17 +39,18 @@ TEST_CASE("ucl: array: push_back", "[ucl]") REQUIRE(arr.front() == 1); REQUIRE(arr.back() == 666); - } TEST_CASE("ucl: array: compare", "[ucl]") { - auto arr = nihil::ucl::array<nihil::ucl::integer>(); + using namespace nihil::ucl; + + auto arr = array<integer>(); arr.push_back(1); arr.push_back(42); arr.push_back(666); - auto arr2 = nihil::ucl::array<nihil::ucl::integer>(); + auto arr2 = array<integer>(); REQUIRE(arr != arr2); arr2.push_back(1); @@ -52,7 +58,7 @@ TEST_CASE("ucl: array: compare", "[ucl]") arr2.push_back(666); REQUIRE(arr == arr2); - auto arr3 = nihil::ucl::array<nihil::ucl::integer>(); + auto arr3 = array<integer>(); arr3.push_back(1); arr3.push_back(1); arr3.push_back(1); @@ -61,10 +67,15 @@ TEST_CASE("ucl: array: compare", "[ucl]") TEST_CASE("ucl: array: iterator", "[ucl]") { - auto arr = nihil::ucl::array<nihil::ucl::integer>{1, 42, 666}; + using namespace nihil::ucl; + + auto arr = array<integer>{1, 42, 666}; auto it = arr.begin(); REQUIRE(*it == 1); + auto end = arr.end(); + REQUIRE(it != end); + REQUIRE(it < end); ++it; REQUIRE(*it == 42); @@ -74,20 +85,26 @@ TEST_CASE("ucl: array: iterator", "[ucl]") --it; REQUIRE(*it == 42); + + ++it; + REQUIRE(it != end); + ++it; + REQUIRE(it == end); } TEST_CASE("ucl: array: parse", "[ucl]") { using namespace std::literals; + using namespace nihil::ucl; auto input = "value = [1, 42, 666]"sv; - auto obj = nihil::ucl::parse(input); + auto obj = parse(input); auto v = obj.lookup("value"); REQUIRE(v); REQUIRE(v->key() == "value"); - auto arr = object_cast<nihil::ucl::array<nihil::ucl::integer>>(*v); + auto arr = object_cast<array<integer>>(*v); REQUIRE(arr.size() == 3); REQUIRE(arr[0] == 1); REQUIRE(arr[1] == 42); @@ -96,7 +113,9 @@ TEST_CASE("ucl: array: parse", "[ucl]") TEST_CASE("ucl: array: emit", "[ucl]") { - auto ucl = nihil::ucl::parse("array = [1, 42, 666];"); + using namespace nihil::ucl; + + auto ucl = parse("array = [1, 42, 666];"); auto output = std::format("{:c}", ucl); REQUIRE(output == "array [\n" @@ -108,7 +127,9 @@ TEST_CASE("ucl: array: emit", "[ucl]") TEST_CASE("ucl: array is a sized_range", "[ucl]") { - auto arr = nihil::ucl::array<nihil::ucl::integer>{1, 42, 666}; + using namespace nihil::ucl; + + auto arr = array<integer>{1, 42, 666}; static_assert(std::ranges::sized_range<decltype(arr)>); auto size = std::ranges::size(arr); @@ -116,19 +137,87 @@ TEST_CASE("ucl: array is a sized_range", "[ucl]") auto begin = std::ranges::begin(arr); static_assert(std::random_access_iterator<decltype(begin)>); + auto end = std::ranges::end(arr); static_assert(std::sentinel_for<decltype(end), decltype(begin)>); REQUIRE(std::distance(begin, end) == 3); - auto vec = std::vector<nihil::ucl::integer>(); + auto vec = std::vector<integer>(); std::ranges::copy(arr, std::back_inserter(vec)); REQUIRE(std::ranges::equal(arr, vec)); auto arr_as_ints = - arr | std::views::transform(&nihil::ucl::integer::value); - auto int_vec = std::vector<nihil::ucl::integer::value_type>(); + arr | std::views::transform(&integer::value); + auto int_vec = std::vector<integer::value_type>(); std::ranges::copy(arr_as_ints, std::back_inserter(int_vec)); REQUIRE(int_vec == std::vector<std::int64_t>{1, 42, 666}); } + +TEST_CASE("ucl: array: bad object_cast", "[ucl]") +{ + using namespace nihil::ucl; + + auto arr = array<integer>(); + + REQUIRE_THROWS_AS(object_cast<integer>(arr), type_mismatch); +} + +TEST_CASE("ucl: array: heterogeneous elements", "[ucl]") +{ + using namespace std::literals; + using namespace nihil::ucl; + + auto obj = parse("array [ 42, true, \"test\" ];"); + auto v = obj.lookup("array"); + REQUIRE(v); + auto arr = object_cast<array<>>(*v); + + REQUIRE(arr.size() == 3); + + auto int_obj = object_cast<integer>(arr[0]); + REQUIRE(int_obj == 42); + + auto bool_obj = object_cast<boolean>(arr[1]); + REQUIRE(bool_obj == true); + + auto string_obj = object_cast<string>(arr[2]); + REQUIRE(string_obj == "test"); +} + +TEST_CASE("ucl: array: heterogenous cast", "[ucl]") +{ + using namespace nihil::ucl; + + auto arr = array<>(); + arr.push_back(integer(42)); + arr.push_back(boolean(true)); + + // Converting to an array<integer> should fail. + REQUIRE_THROWS_AS(object_cast<array<integer>>(arr), type_mismatch); + + // Converting to array<object> should succeed. + auto obj_arr = object_cast<array<object>>(arr); + REQUIRE(obj_arr[0] == integer(42)); +} + +TEST_CASE("ucl: array: homogeneous cast", "[ucl]") +{ + using namespace nihil::ucl; + + auto arr = array<>(); + arr.push_back(integer(1)); + arr.push_back(integer(42)); + + auto obj = object(arr.get_ucl_object()); + + // Converting to array<string> should fail. + REQUIRE_THROWS_AS(object_cast<array<string>>(obj), type_mismatch); + + // Converting to an array<integer> should succeed. + auto obj_arr = object_cast<array<integer>>(obj); + REQUIRE(obj_arr[0] == 1); + REQUIRE(obj_arr[1] == 42); +} + diff --git a/nihil.ucl/type.ccm b/nihil.ucl/type.ccm new file mode 100644 index 0000000..2aef1a6 --- /dev/null +++ b/nihil.ucl/type.ccm @@ -0,0 +1,93 @@ +/* + * This source code is released into the public domain. + */ + +module; + +#include <concepts> +#include <string> + +#include <ucl.h> + +export module nihil.ucl:type; + +import :error; + +namespace nihil::ucl { + +// Our strongly-typed version of ::ucl_type. +export enum struct object_type { + object = UCL_OBJECT, + array = UCL_ARRAY, + integer = UCL_INT, + real = UCL_FLOAT, + string = UCL_STRING, + boolean = UCL_BOOLEAN, + time = UCL_TIME, + userdata = UCL_USERDATA, + null = UCL_NULL, +}; + +// Get the name of a type. +export auto str(object_type type) -> std::string_view { + using namespace std::literals; + + switch (type) { + case object_type::object: + return "object"sv; + case object_type::array: + return "array"sv; + case object_type::integer: + return "integer"sv; + case object_type::real: + return "real"sv; + case object_type::string: + return "string"sv; + case object_type::boolean: + return "boolean"sv; + case object_type::time: + return "time"sv; + case object_type::userdata: + return "userdata"sv; + case object_type::null: + return "null"sv; + default: + // Don't fail here, since UCL might add more types that we + // don't know about. + return "unknown"sv; + } +} + +// Concept of a UCL data type. +export template<typename T> +concept datatype = requires(T o) { + { o.get_ucl_object() } -> std::convertible_to<::ucl_object_t const *>; + { o.type() } -> std::same_as<object_type>; + { T::ucl_type } -> std::convertible_to<object_type>; +}; + +// Exception thrown when a type assertion fails. +export struct type_mismatch : error { + type_mismatch(object_type expected_type, object_type actual_type) + : error("UCL type mismatch: expected type '{}' != actual type '{}'", + str(expected_type), str(actual_type)) + , _expected_type(expected_type) + , _actual_type(actual_type) + {} + + // The type we expected. + auto expected_type(this type_mismatch const &self) -> object_type { + return self._expected_type; + } + + // The type we got. + auto actual_type(this type_mismatch const &self) -> object_type { + return self._actual_type; + } + +private: + object_type _expected_type; + object_type _actual_type; +}; + +} // namespace nihil::ucl |
