/* * This source code is released into the public domain. */ module; #include #include #include #include #include #include #include #include #include #include export module nihil.ucl:emit; import :object; namespace nihil::ucl { export enum struct emitter { configuration = UCL_EMIT_CONFIG, compact_json = UCL_EMIT_JSON_COMPACT, json = UCL_EMIT_JSON, yaml = UCL_EMIT_YAML, }; /* * Wrap ucl_emitter_functions for a particular output iterator type. * * We can't throw exceptions here since we're called from C code. The emit * functions return an integer value, but it's not really clear what this is * for and the C API seems to mostly ignore it. So, we just eat errors and * keep going. */ template Iterator> struct emit_wrapper { emit_wrapper(Iterator iterator_) : iterator(std::move(iterator_)) {} static auto append_character(unsigned char c, std::size_t nchars, void *ud) noexcept -> int try { auto *self = static_cast(ud); while (nchars--) *self->iterator++ = static_cast(c); return 0; } catch (...) { return 0; } static auto append_len(unsigned char const *str, std::size_t len, void *ud) noexcept -> int try { auto *self = static_cast(ud); for (auto c : std::span(str, len)) *self->iterator++ = static_cast(c); return 0; } catch (...) { return 0; } static auto append_int(std::int64_t value, void *ud) noexcept -> int try { auto constexpr bufsize = std::numeric_limits::digits10; auto buf = std::array(); auto *self = static_cast(ud); auto result = std::to_chars(buf.data(), buf.data() + buf.size(), value, 10); if (result.ec == std::errc()) for (auto c : std::span(buf.data(), result.ptr)) *self->iterator++ = c; return 0; } catch (...) { return 0; } static auto append_double(double value, void *ud) noexcept -> int try { auto constexpr bufsize = std::numeric_limits::digits10; auto buf = std::array(); auto *self = static_cast(ud); auto result = std::to_chars(buf.data(), buf.data() + buf.size(), value); if (result.ec == std::errc()) for (auto c : std::span(buf.data(), result.ptr)) *self->iterator++ = c; return 0; } catch (...) { return 0; } auto get_functions(this emit_wrapper &self) -> ucl_emitter_functions { auto ret = ucl_emitter_functions{}; ret.ucl_emitter_append_character = &emit_wrapper::append_character; ret.ucl_emitter_append_len = &emit_wrapper::append_len; ret.ucl_emitter_append_int = &emit_wrapper::append_int; ret.ucl_emitter_append_double = &emit_wrapper::append_double; ret.ud = &self; return ret; } private: Iterator iterator{}; }; export auto emit(object const &object, emitter format, std::output_iterator auto &&it) -> void { auto ucl_format = static_cast(format); auto wrapper = emit_wrapper(it); auto functions = wrapper.get_functions(); ::ucl_object_emit_full(object.get_ucl_object(), ucl_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 &; } // namespace nihil::ucl /* * Specialisation of std::formatter<> for object. */ template T> 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; } };