// This source code is released into the public domain. module; #include export module nihil.ucl:emit; import nihil.std; import :object; namespace nihil::ucl { export enum struct emitter : std::uint8_t { 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 { explicit emit_wrapper(Iterator iterator) : m_iterator(std::move(iterator)) { } static auto append_character(unsigned char const c, std::size_t nchars, void *const ud) noexcept -> int try { auto &self = check_magic(ud); self.m_iterator = std::ranges::fill_n(self.m_iterator, nchars, static_cast(c)); return 0; } catch (...) { return 0; } static auto append_len(unsigned char const *const str, std::size_t const len, void *const ud) noexcept -> int try { auto &self = check_magic(ud); self.m_iterator = std::ranges::copy(std::span(str, len), self.m_iterator).out; return 0; } catch (...) { return 0; } static auto append_int(std::int64_t const value, void *const ud) noexcept -> int try { auto &self = check_magic(ud); auto buf = std::array::digits10>(); auto result = std::to_chars(begin(buf), end(buf), value, 10); if (result.ec == std::errc()) { auto chars = std::span(buf.data(), result.ptr); self.m_iterator = std::ranges::copy(chars, self.m_iterator).out; } return 0; } catch (...) { return 0; } static auto append_double(double const value, void *const ud) noexcept -> int try { auto &self = check_magic(ud); auto buf = std::array::digits10>(); auto result = std::to_chars(begin(buf), end(buf), value); if (result.ec == std::errc()) { auto chars = std::span(buf.data(), result.ptr); self.m_iterator = std::ranges::copy(chars, self.m_iterator).out; } 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; } [[nodiscard]] auto iterator(this emit_wrapper &self) -> Iterator & { return self.m_iterator; } [[nodiscard]] auto iterator(this emit_wrapper const &self) -> Iterator const & { return self.m_iterator; } private: Iterator m_iterator{}; std::uint64_t m_magic = wrapper_magic; // Harden against memory errors. static constexpr auto wrapper_magic = std::uint32_t{0x57524150}; static auto check_magic(void *p) -> emit_wrapper & { auto *ret = static_cast(p); if (ret->m_magic != wrapper_magic) throw std::runtime_error("Invalid emit_wrapper pointer"); return *ret; } }; export auto emit(object const &object, emitter const 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. Note that most derived UCL types override this. 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. Note that most derived // UCL types override this. template T> struct std::formatter { nihil::ucl::emitter emitter = nihil::ucl::emitter::json; template constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator { 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 auto format(nihil::ucl::object const &o, FmtContext &ctx) const -> FmtContext::iterator { // 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 wrapper.iterator(); } };