From a14cb70cb715beb714e7b05d8fe631c0df4a7053 Mon Sep 17 00:00:00 2001 From: Lexi Winter Date: Sun, 22 Jun 2025 15:22:23 +0100 Subject: nihil.ucl: add object comparison and std::format support --- nihil.ucl/emit.ccm | 73 +++++++++++++++++++++++++++++++++++++++++ nihil.ucl/object.ccm | 23 +++++++++++++ nihil.ucl/tests/emit.cc | 82 +++++++++++++++++++++++++++++++++++++++++++++++ nihil.ucl/tests/object.cc | 15 +++++++++ 4 files changed, 193 insertions(+) (limited to 'nihil.ucl') diff --git a/nihil.ucl/emit.ccm b/nihil.ucl/emit.ccm index 8fdf616..a451f88 100644 --- a/nihil.ucl/emit.ccm +++ b/nihil.ucl/emit.ccm @@ -7,7 +7,9 @@ module; #include #include #include +#include #include +#include #include #include #include @@ -138,4 +140,75 @@ export auto emit(object const &object, emitter format, &functions, nullptr); } +/* + * 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 & +{ + emit(o, emitter::json, std::ostream_iterator(stream)); + return stream; +} + } // namespace nihil::ucl + +/* + * Specialisation of std::formatter<> for object. + */ +template<> +struct std::formatter +{ + nihil::ucl::emitter emitter = nihil::ucl::emitter::json; + + template + constexpr ParseContext::iterator parse(ParseContext& ctx) + { + auto it = ctx.begin(); + auto end = ctx.end(); + + while (it != end) { + switch (*it) { + case 'j': + emitter = nihil::ucl::emitter::json; + break; + case 'J': + emitter = nihil::ucl::emitter::compact_json; + break; + case 'c': + emitter = nihil::ucl::emitter::configuration; + break; + case 'y': + emitter = nihil::ucl::emitter::yaml; + break; + case '}': + return it; + default: + throw std::format_error("Invalid format string " + "for UCL object"); + } + + ++it; + } + + return it; + } + + template + FmtContext::iterator format(nihil::ucl::object const &o, + FmtContext& ctx) const + { + // We can't use emit() here since the context iterator is not + // an std::output_iterator. + + auto out = ctx.out(); + + auto ucl_format = static_cast<::ucl_emitter>(emitter); + auto wrapper = nihil::ucl::emit_wrapper(out); + auto functions = wrapper.get_functions(); + + ::ucl_object_emit_full(o.get_ucl_object(), ucl_format, + &functions, nullptr); + return out; + } +}; diff --git a/nihil.ucl/object.ccm b/nihil.ucl/object.ccm index 0b8c95f..43f36fe 100644 --- a/nihil.ucl/object.ccm +++ b/nihil.ucl/object.ccm @@ -169,6 +169,29 @@ private: friend struct iterator; }; +/*********************************************************************** + * Object comparison. + */ + +export auto operator<=>(object const &lhs, object const &rhs) + -> std::strong_ordering +{ + auto cmp = ::ucl_object_compare(lhs.get_ucl_object(), + rhs.get_ucl_object()); + + if (cmp < 0) + return std::strong_ordering::less; + else if (cmp > 0) + return std::strong_ordering::greater; + else + return std::strong_ordering::equal; +} + +export auto operator==(object const &lhs, object const &rhs) -> bool +{ + return (lhs <=> rhs) == std::strong_ordering::equal; +} + /*********************************************************************** * Object iteration. */ diff --git a/nihil.ucl/tests/emit.cc b/nihil.ucl/tests/emit.cc index 13f5914..c7f9757 100644 --- a/nihil.ucl/tests/emit.cc +++ b/nihil.ucl/tests/emit.cc @@ -2,7 +2,89 @@ * This source code is released into the public domain. */ +#include +#include + #include import nihil; import nihil.ucl; + +TEST_CASE("ucl: emit to std::ostream", "[ucl]") +{ + using namespace std::literals; + + auto obj = nihil::ucl::parse("int = [1, 42, 666]"sv); + auto strm = std::ostringstream(); + strm << obj; + + REQUIRE(strm.str() == +"{\n" +" \"int\": [\n" +" 1,\n" +" 42,\n" +" 666\n" +" ]\n" +"}"); +} + +TEST_CASE("ucl: emit JSON with std::format", "[ucl]") +{ + using namespace std::literals; + + auto obj = nihil::ucl::parse("int = [1, 42, 666]"sv); + auto str = std::format("{:j}", obj); + + REQUIRE(str == +"{\n" +" \"int\": [\n" +" 1,\n" +" 42,\n" +" 666\n" +" ]\n" +"}"); + + // Make sure JSON is the default format. + auto str2 = std::format("{}", obj); + REQUIRE(str == str2); +} + +TEST_CASE("ucl: emit compact JSON with std::format", "[ucl]") +{ + using namespace std::literals; + + auto obj = nihil::ucl::parse("int = [1, 42, 666]"sv); + auto str = std::format("{:J}", obj); + + REQUIRE(str == "{\"int\":[1,42,666]}"); +} + +TEST_CASE("ucl: emit configuration with std::format", "[ucl]") +{ + using namespace std::literals; + + auto obj = nihil::ucl::parse("int = [1, 42, 666]"sv); + auto str = std::format("{:c}", obj); + + REQUIRE(str == +"int [\n" +" 1,\n" +" 42,\n" +" 666,\n" +"]\n"); +} + +TEST_CASE("ucl: emit YAML with std::format", "[ucl]") +{ + using namespace std::literals; + + auto obj = nihil::ucl::parse("int = [1, 42, 666]"sv); + auto str = std::format("{:y}", obj); + + REQUIRE(str == +"int: [\n" +" 1,\n" +" 42,\n" +" 666\n" +"]"); +} diff --git a/nihil.ucl/tests/object.cc b/nihil.ucl/tests/object.cc index f9cef9f..1bbcf4f 100644 --- a/nihil.ucl/tests/object.cc +++ b/nihil.ucl/tests/object.cc @@ -20,3 +20,18 @@ TEST_CASE("ucl object: get_ucl_object", "[ucl]") static_assert(std::same_as<::ucl_object_t const *, decltype(cobj.get_ucl_object())>); } + +TEST_CASE("ucl object: compare", "[ucl]") +{ + using namespace std::literals; + + auto obj_41 = nihil::ucl::parse("int = 41;"sv); + auto obj_42 = nihil::ucl::parse("int = 42;"sv); + auto obj_42_2 = nihil::ucl::parse("int = 42;"sv); + auto obj_43 = nihil::ucl::parse("int = 43;"sv); + + REQUIRE(obj_42 == obj_42_2); + REQUIRE(obj_42 != obj_43); + REQUIRE(obj_42 < obj_43); + REQUIRE(obj_42 > obj_41); +} -- cgit v1.2.3