// This source code is released into the public domain. #include #include import nihil.std; import nihil.ucl; namespace { TEST_CASE("ucl: array: invariants", "[ucl]") { using namespace nihil::ucl; REQUIRE(array<>::ucl_type == object_type::array); REQUIRE(static_cast<::ucl_type>(array<>::ucl_type) == UCL_ARRAY); static_assert(std::destructible>); static_assert(std::default_initializable>); static_assert(std::move_constructible>); static_assert(std::copy_constructible>); static_assert(std::equality_comparable>); static_assert(std::totally_ordered>); static_assert(std::swappable>); static_assert(std::ranges::sized_range>); static_assert(std::same_as>, integer>); } TEST_CASE("ucl: array: constructor", "[ucl]") { using namespace nihil::ucl; SECTION("default") { auto arr = array(); REQUIRE(arr.size() == 0); REQUIRE(str(arr.type()) == "array"); } SECTION("from range") { auto vec = std::vector{integer(1), integer(42)}; auto arr = array(vec); REQUIRE(arr.size() == 2); REQUIRE(arr[0] == 1); REQUIRE(arr[1] == 42); } SECTION("from iterator pair") { auto vec = std::vector{integer(1), integer(42)}; auto arr = array(std::ranges::begin(vec), std::ranges::end(vec)); REQUIRE(arr.size() == 2); REQUIRE(arr[0] == 1); REQUIRE(arr[1] == 42); } SECTION("from initializer_list") { auto arr = array{integer(1), integer(42)}; REQUIRE(arr.size() == 2); REQUIRE(arr[0] == 1); REQUIRE(arr[1] == 42); } } TEST_CASE("ucl: array: construct from UCL object", "[ucl]") { using namespace nihil::ucl; SECTION("ref, correct type") { auto *uarr = ::ucl_object_typed_new(UCL_ARRAY); auto *uint = ::ucl_object_fromint(42); ::ucl_array_append(uarr, uint); auto arr = array(ref, uarr); REQUIRE(arr[0] == 42); ::ucl_object_unref(uarr); } SECTION("noref, correct type") { auto *uarr = ::ucl_object_typed_new(UCL_ARRAY); auto *uint = ::ucl_object_fromint(42); ::ucl_array_append(uarr, uint); auto arr = array(noref, uarr); REQUIRE(arr[0] == 42); } SECTION("ref, wrong element type") { auto *uarr = ::ucl_object_typed_new(UCL_ARRAY); auto *uint = ::ucl_object_frombool(true); ::ucl_array_append(uarr, uint); auto arr = array(noref, uarr); REQUIRE_THROWS_AS(arr[0], type_mismatch); } SECTION("ref, wrong type") { auto *uobj = ::ucl_object_frombool(true); REQUIRE_THROWS_AS(array(ref, uobj), type_mismatch); ::ucl_object_unref(uobj); } SECTION("noref, wrong type") { auto *uobj = ::ucl_object_frombool(true); REQUIRE_THROWS_AS(array(noref, uobj), type_mismatch); ::ucl_object_unref(uobj); } } TEST_CASE("ucl: array: swap", "[ucl]") { // do not add using namespace nihil::ucl auto arr1 = nihil::ucl::array{nihil::ucl::integer(1), nihil::ucl::integer(2)}; auto arr2 = nihil::ucl::array{ nihil::ucl::integer(3), }; swap(arr1, arr2); REQUIRE(arr1.size() == 1); REQUIRE(arr1[0] == 3); REQUIRE(arr2.size() == 2); REQUIRE(arr2[0] == 1); } TEST_CASE("ucl: array: push_back", "[ucl]") { using namespace nihil::ucl; auto arr = array(); REQUIRE(arr.size() == 0); arr.push_back(integer(1)); arr.push_back(integer(42)); arr.push_back(integer(666)); REQUIRE(arr.size() == 3); REQUIRE(arr[0] == 1); REQUIRE(arr[1] == 42); REQUIRE(arr[2] == 666); REQUIRE_THROWS_AS(arr[3], std::out_of_range); REQUIRE(arr.front() == 1); REQUIRE(arr.back() == 666); } TEST_CASE("ucl: array: compare", "[ucl]") { using namespace nihil::ucl; auto arr = array{integer(1), integer(42), integer(666)}; auto arr2 = array(); REQUIRE(arr != arr2); arr2.push_back(integer(1)); arr2.push_back(integer(42)); arr2.push_back(integer(666)); REQUIRE(arr == arr2); auto arr3 = array{integer(1), integer(1), integer(1)}; REQUIRE(arr != arr3); } TEST_CASE("ucl: array: iterator", "[ucl]") { using namespace nihil::ucl; auto arr = array{integer(1), integer(42), integer(666)}; auto it = arr.begin(); REQUIRE(*it == 1); auto end = arr.end(); REQUIRE(it != end); REQUIRE(it < end); ++it; REQUIRE(*it == 42); ++it; REQUIRE(*it == 666); --it; REQUIRE(*it == 42); ++it; REQUIRE(it != end); ++it; REQUIRE(it == end); } TEST_CASE("ucl: array: parse", "[ucl]") { using namespace std::literals; using namespace nihil::ucl; auto obj = parse("value = [1, 42, 666]"sv).value(); auto arr = object_cast>(obj["value"]).value(); REQUIRE(arr.size() == 3); REQUIRE(arr[0] == 1); REQUIRE(arr[1] == 42); REQUIRE(arr[2] == 666); } TEST_CASE("ucl: array: emit", "[ucl]") { using namespace nihil::ucl; auto ucl = parse("array = [1, 42, 666];").value(); auto output = std::format("{:c}", ucl); REQUIRE(output == "array [\n" " 1,\n" " 42,\n" " 666,\n" "]\n"); } TEST_CASE("ucl: array: format", "[ucl]") { using namespace nihil::ucl; SECTION("empty array") { auto arr = array(); REQUIRE(std::format("{}", arr) == "[]"); } SECTION("bare array") { auto arr = array{integer(1), integer(42), integer(666)}; auto output = std::format("{}", arr); REQUIRE(output == "[1, 42, 666]"); } SECTION("parsed array") { auto ucl = parse("array = [1, 42, 666];").value(); auto arr = object_cast>(ucl["array"]).value(); auto output = std::format("{}", arr); REQUIRE(output == "[1, 42, 666]"); } } TEST_CASE("ucl: array: print to ostream", "[ucl]") { using namespace nihil::ucl; SECTION("empty array") { auto arr = array(); auto strm = std::ostringstream(); strm << arr; REQUIRE(strm.str() == "[]"); } SECTION("bare array") { auto arr = array{integer(1), integer(42), integer(666)}; auto strm = std::ostringstream(); strm << arr; REQUIRE(strm.str() == "[1, 42, 666]"); } SECTION("parsed array") { auto ucl = parse("array = [1, 42, 666];").value(); auto arr = object_cast>(ucl["array"]).value(); auto strm = std::ostringstream(); strm << arr; REQUIRE(strm.str() == "[1, 42, 666]"); } } TEST_CASE("ucl: array is a sized_range", "[ucl]") { using namespace nihil::ucl; auto arr = array{integer(1), integer(42), integer(666)}; auto size = std::ranges::size(arr); REQUIRE(size == 3); auto begin = std::ranges::begin(arr); static_assert(std::random_access_iterator); auto end = std::ranges::end(arr); static_assert(std::sentinel_for); REQUIRE(std::distance(begin, end) == 3); auto vec = std::vector(); std::ranges::copy(arr, std::back_inserter(vec)); REQUIRE(std::ranges::equal(arr, vec)); auto arr_as_ints = arr | std::views::transform(&integer::value); auto int_vec = std::vector(); std::ranges::copy(arr_as_ints, std::back_inserter(int_vec)); REQUIRE(int_vec == std::vector{1, 42, 666}); } TEST_CASE("ucl: array: bad object_cast", "[ucl]") { using namespace nihil::ucl; auto arr = array(); auto cast_ok = object_cast(arr); REQUIRE(!cast_ok); } TEST_CASE("ucl: array: heterogeneous elements", "[ucl]") { using namespace std::literals; using namespace nihil::ucl; auto obj_err = parse("array [ 42, true, \"test\" ];"); REQUIRE(obj_err); auto obj = *obj_err; auto err = object_cast>(obj["array"]); REQUIRE(err); auto arr = *err; REQUIRE(arr.size() == 3); auto int_obj = object_cast(arr[0]); REQUIRE(int_obj); REQUIRE(*int_obj == 42); auto bool_obj = object_cast(arr[1]); REQUIRE(bool_obj); REQUIRE(*bool_obj == true); auto string_obj = object_cast(arr[2]); REQUIRE(string_obj); REQUIRE(*string_obj == "test"); } TEST_CASE("ucl: array: heterogenous cast", "[ucl]") { using namespace nihil::ucl; auto arr = array<>(); arr.push_back(integer(42)); arr.push_back(boolean(true)); // Converting to an array should fail. auto cast_ok = object_cast>(arr); REQUIRE(!cast_ok); // Converting to array should succeed. auto err = object_cast>(arr); REQUIRE(err); auto obj_arr = *err; REQUIRE(obj_arr[0] == integer(42)); } TEST_CASE("ucl: array: homogeneous cast", "[ucl]") { using namespace nihil::ucl; auto arr = array<>(); arr.push_back(integer(1)); arr.push_back(integer(42)); auto obj = object(ref, arr.get_ucl_object()); // Converting to array should fail. auto cast_ok = object_cast>(obj); REQUIRE(!cast_ok); // Converting to an array should succeed. auto err = object_cast>(obj); REQUIRE(err); auto obj_arr = *err; REQUIRE(obj_arr[0] == 1); REQUIRE(obj_arr[1] == 42); } TEST_CASE("array iterator: empty iterator", "[ucl]") { using namespace nihil::ucl; auto it = array_iterator(); REQUIRE_THROWS_AS(*it, std::logic_error); REQUIRE_THROWS_AS(it[0], std::logic_error); REQUIRE_THROWS_AS(it++, std::logic_error); REQUIRE_THROWS_AS(++it, std::logic_error); auto it2 = array_iterator(); REQUIRE(it == it2); REQUIRE((it < it2) == false); REQUIRE((it > it2) == false); } TEST_CASE("array iterator: invalid operations", "[ucl]") { using namespace nihil::ucl; auto arr = array{integer(42)}; auto it = arr.begin(); SECTION("decrement before start") { REQUIRE_THROWS_AS(--it, std::logic_error); REQUIRE_THROWS_AS(it--, std::logic_error); REQUIRE_THROWS_AS(it - 1, std::logic_error); } SECTION("increment past end") { ++it; REQUIRE(it == arr.end()); REQUIRE_THROWS_AS(++it, std::logic_error); REQUIRE_THROWS_AS(it++, std::logic_error); REQUIRE_THROWS_AS(it + 1, std::logic_error); } SECTION("dereference iterator at end") { REQUIRE_THROWS_AS(it[1], std::logic_error); ++it; REQUIRE(it == arr.end()); REQUIRE_THROWS_AS(*it, std::logic_error); } SECTION("compare with different array") { auto arr2 = array{integer(42)}; REQUIRE_THROWS_AS(it == arr2.begin(), std::logic_error); REQUIRE_THROWS_AS(it > arr2.begin(), std::logic_error); REQUIRE_THROWS_AS(it - arr2.begin(), std::logic_error); } SECTION("compare with empty iterator") { auto it2 = array_iterator(); REQUIRE_THROWS_AS(it == it2, std::logic_error); REQUIRE_THROWS_AS(it > it2, std::logic_error); REQUIRE_THROWS_AS(it - it2, std::logic_error); } } } // anonymous namespace